Something went wrong. Try again.
🎥 Command line media player
Something went wrong. Try again.
C
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461/* * This file is part of mpv. * * mpv 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. * * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>. */
#include <assert.h>#include <errno.h>#include <fcntl.h>#include <stdbool.h>#include <sys/mman.h>#include <poll.h>#include <unistd.h>
#include <drm_fourcc.h>
#include "common/msg.h"#include "drm_atomic.h"#include "drm_common.h"#include "osdep/timer.h"#include "sub/osd.h"#include "video/fmt-conversion.h"#include "video/mp_image.h"#include "video/out/present_sync.h"#include "video/sws_utils.h"#include "vo.h"
#define IMGFMT_XRGB8888 IMGFMT_BGR0#define IMGFMT_XBGR8888 IMGFMT_RGB0#define IMGFMT_XRGB2101010 \ pixfmt2imgfmt(MP_SELECT_LE_BE(AV_PIX_FMT_X2RGB10LE, AV_PIX_FMT_X2RGB10BE))#define IMGFMT_XBGR2101010 \ pixfmt2imgfmt(MP_SELECT_LE_BE(AV_PIX_FMT_X2BGR10LE, AV_PIX_FMT_X2BGR10BE))#define IMGFMT_YUYV pixfmt2imgfmt(AV_PIX_FMT_YUYV422)
#define BYTES_PER_PIXEL 4#define BITS_PER_PIXEL 32
struct drm_frame { struct framebuffer *fb;};
struct priv { struct drm_frame **fb_queue; unsigned int fb_queue_len;
uint32_t drm_format; enum mp_imgfmt imgfmt;
struct mp_image *last_input; struct mp_image *cur_frame; struct mp_image *cur_frame_cropped; struct mp_rect src; struct mp_rect dst; struct mp_osd_res osd; struct mp_sws_context *sws;
struct framebuffer **bufs; int front_buf; int buf_count;};
static void destroy_framebuffer(int fd, struct framebuffer *fb){ if (!fb) return;
if (fb->map) { munmap(fb->map, fb->size); } if (fb->id) { drmModeRmFB(fd, fb->id); } if (fb->handle) { struct drm_mode_destroy_dumb dreq = { .handle = fb->handle, }; drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); }}
static struct framebuffer *setup_framebuffer(struct vo *vo){ struct priv *p = vo->priv; struct vo_drm_state *drm = vo->drm;
struct framebuffer *fb = talloc_zero(drm, struct framebuffer); fb->width = drm->mode.mode.hdisplay; fb->height = drm->mode.mode.vdisplay; fb->fd = drm->fd; fb->handle = 0;
// create dumb buffer struct drm_mode_create_dumb creq = { .width = fb->width, .height = fb->height, .bpp = BITS_PER_PIXEL, };
if (drmIoctl(drm->fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq) < 0) { MP_ERR(vo, "Cannot create dumb buffer: %s\n", mp_strerror(errno)); goto err; }
fb->stride = creq.pitch; fb->size = creq.size; fb->handle = creq.handle;
// select format switch (drm->opts->drm_format) { case DRM_OPTS_FORMAT_XRGB2101010: p->drm_format = DRM_FORMAT_XRGB2101010; p->imgfmt = IMGFMT_XRGB2101010; break; case DRM_OPTS_FORMAT_XBGR2101010: p->drm_format = DRM_FORMAT_XRGB2101010; p->imgfmt = IMGFMT_XRGB2101010; break; case DRM_OPTS_FORMAT_XBGR8888: p->drm_format = DRM_FORMAT_XBGR8888; p->imgfmt = IMGFMT_XBGR8888; break; case DRM_OPTS_FORMAT_YUYV: p->drm_format = DRM_FORMAT_YUYV; p->imgfmt = IMGFMT_YUYV; break; default: if (drm->opts->drm_format != DRM_OPTS_FORMAT_XRGB8888) { MP_VERBOSE(vo, "Requested format not supported by VO, " "falling back to xrgb8888\n"); } p->drm_format = DRM_FORMAT_XRGB8888; p->imgfmt = IMGFMT_XRGB8888; break; }
// create framebuffer object for the dumb-buffer int ret = drmModeAddFB2(fb->fd, fb->width, fb->height, p->drm_format, (uint32_t[4]){fb->handle, 0, 0, 0}, (uint32_t[4]){fb->stride, 0, 0, 0}, (uint32_t[4]){0, 0, 0, 0}, &fb->id, 0); if (ret) { MP_ERR(vo, "Cannot create framebuffer: %s\n", mp_strerror(errno)); goto err; }
// prepare buffer for memory mapping struct drm_mode_map_dumb mreq = { .handle = fb->handle, }; if (drmIoctl(drm->fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq)) { MP_ERR(vo, "Cannot map dumb buffer: %s\n", mp_strerror(errno)); goto err; }
// perform actual memory mapping fb->map = mmap(0, fb->size, PROT_READ | PROT_WRITE, MAP_SHARED, drm->fd, mreq.offset); if (fb->map == MAP_FAILED) { MP_ERR(vo, "Cannot map dumb buffer: %s\n", mp_strerror(errno)); goto err; }
memset(fb->map, 0, fb->size); return fb;
err: destroy_framebuffer(drm->fd, fb); return NULL;}
static int reconfig(struct vo *vo, struct mp_image_params *params){ struct priv *p = vo->priv; struct vo_drm_state *drm = vo->drm;
vo->dwidth =drm->fb->width; vo->dheight = drm->fb->height; vo_get_src_dst_rects(vo, &p->src, &p->dst, &p->osd);
struct mp_imgfmt_desc fmt = mp_imgfmt_get_desc(p->imgfmt); p->dst.x0 = MP_ALIGN_DOWN(p->dst.x0, fmt.align_x); p->dst.y0 = MP_ALIGN_DOWN(p->dst.y0, fmt.align_y);
p->sws->src = *params; p->sws->dst = (struct mp_image_params) { .imgfmt = p->imgfmt, .w = mp_rect_w(p->dst), .h = mp_rect_h(p->dst), .p_w = 1, .p_h = 1, };
talloc_free(p->cur_frame); p->cur_frame = mp_image_alloc(p->imgfmt, drm->fb->width, drm->fb->height); mp_image_params_guess_csp(&p->sws->dst); mp_image_set_params(p->cur_frame, &p->sws->dst); mp_image_set_size(p->cur_frame, drm->fb->width, drm->fb->height);
talloc_free(p->cur_frame_cropped); p->cur_frame_cropped = mp_image_new_dummy_ref(p->cur_frame); mp_image_crop_rc(p->cur_frame_cropped, p->dst);
talloc_free(p->last_input); p->last_input = NULL;
if (mp_sws_reinit(p->sws) < 0) return -1;
mp_mutex_lock(&vo->params_mutex); vo->target_params = &p->sws->dst; // essentially constant, so this is okay mp_mutex_unlock(&vo->params_mutex); vo->want_redraw = true; return 0;}
static struct framebuffer *get_new_fb(struct vo *vo){ struct priv *p = vo->priv;
p->front_buf++; p->front_buf %= p->buf_count;
return p->bufs[p->front_buf];}
static void draw_image(struct vo *vo, mp_image_t *mpi, struct framebuffer *buf){ struct priv *p = vo->priv; struct vo_drm_state *drm = vo->drm;
if (drm->active && buf != NULL) { if (mpi) { struct mp_image src = *mpi; struct mp_rect src_rc = p->src; src_rc.x0 = MP_ALIGN_DOWN(src_rc.x0, mpi->fmt.align_x); src_rc.y0 = MP_ALIGN_DOWN(src_rc.y0, mpi->fmt.align_y); mp_image_crop_rc(&src, src_rc);
mp_image_clear(p->cur_frame, 0, 0, p->cur_frame->w, p->dst.y0); mp_image_clear(p->cur_frame, 0, p->dst.y1, p->cur_frame->w, p->cur_frame->h); mp_image_clear(p->cur_frame, 0, p->dst.y0, p->dst.x0, p->dst.y1); mp_image_clear(p->cur_frame, p->dst.x1, p->dst.y0, p->cur_frame->w, p->dst.y1);
mp_sws_scale(p->sws, p->cur_frame_cropped, &src); osd_draw_on_image(vo->osd, p->osd, src.pts, 0, p->cur_frame); } else { mp_image_clear(p->cur_frame, 0, 0, p->cur_frame->w, p->cur_frame->h); osd_draw_on_image(vo->osd, p->osd, 0, 0, p->cur_frame); }
memcpy_pic(buf->map, p->cur_frame->planes[0], p->cur_frame->w * BYTES_PER_PIXEL, p->cur_frame->h, buf->stride, p->cur_frame->stride[0]); }
if (mpi != p->last_input) { talloc_free(p->last_input); p->last_input = mpi; }}
static void enqueue_frame(struct vo *vo, struct framebuffer *fb){ struct priv *p = vo->priv;
struct drm_frame *new_frame = talloc(p, struct drm_frame); new_frame->fb = fb; MP_TARRAY_APPEND(p, p->fb_queue, p->fb_queue_len, new_frame);}
static void dequeue_frame(struct vo *vo){ struct priv *p = vo->priv;
talloc_free(p->fb_queue[0]); MP_TARRAY_REMOVE_AT(p->fb_queue, p->fb_queue_len, 0);}
static void swapchain_step(struct vo *vo){ struct priv *p = vo->priv;
if (p->fb_queue_len > 0) { dequeue_frame(vo); }}
static bool draw_frame(struct vo *vo, struct vo_frame *frame){ struct vo_drm_state *drm = vo->drm; struct priv *p = vo->priv;
if (!drm->active) goto done;
drm->still = frame->still;
// we redraw the entire image when OSD needs to be redrawn struct framebuffer *fb = p->bufs[p->front_buf]; const bool repeat = frame->repeat && !frame->redraw; if (!repeat) { fb = get_new_fb(vo); draw_image(vo, mp_image_new_ref(frame->current), fb); }
enqueue_frame(vo, fb);
done: return VO_TRUE;}
static void queue_flip(struct vo *vo, struct drm_frame *frame){ struct vo_drm_state *drm = vo->drm;
drm->fb = frame->fb;
int ret = drmModePageFlip(drm->fd, drm->crtc_id, drm->fb->id, DRM_MODE_PAGE_FLIP_EVENT, drm); if (ret) MP_WARN(vo, "Failed to queue page flip: %s\n", mp_strerror(errno)); drm->waiting_for_flip = !ret;}
static void flip_page(struct vo *vo){ struct priv *p = vo->priv; struct vo_drm_state *drm = vo->drm; const bool drain = drm->paused || drm->still;
if (!drm->active) return;
while (drain || p->fb_queue_len > vo->opts->swapchain_depth) { if (drm->waiting_for_flip) { vo_drm_wait_on_flip(vo->drm); swapchain_step(vo); } if (p->fb_queue_len <= 1) break; if (!p->fb_queue[1] || !p->fb_queue[1]->fb) { MP_ERR(vo, "Hole in swapchain?\n"); swapchain_step(vo); continue; } queue_flip(vo, p->fb_queue[1]); }}
static void get_vsync(struct vo *vo, struct vo_vsync_info *info){ struct vo_drm_state *drm = vo->drm; present_sync_get_info(drm->present, info);}
static void uninit(struct vo *vo){ struct priv *p = vo->priv;
vo_drm_uninit(vo);
while (p->fb_queue_len > 0) { swapchain_step(vo); }
talloc_free(p->last_input); talloc_free(p->cur_frame); talloc_free(p->cur_frame_cropped);}
static int preinit(struct vo *vo){ struct priv *p = vo->priv;
if (!vo_drm_init(vo)) goto err;
struct vo_drm_state *drm = vo->drm; p->buf_count = vo->opts->swapchain_depth + 1; p->bufs = talloc_zero_array(p, struct framebuffer *, p->buf_count);
p->front_buf = 0; for (int i = 0; i < p->buf_count; i++) { p->bufs[i] = setup_framebuffer(vo); if (!p->bufs[i]) goto err; } drm->fb = p->bufs[0];
vo->drm->width = vo->drm->fb->width; vo->drm->height = vo->drm->fb->height;
if (!vo_drm_acquire_crtc(vo->drm)) { MP_ERR(vo, "Failed to set CRTC for connector %u: %s\n", vo->drm->connector->connector_id, mp_strerror(errno)); goto err; }
vo_drm_set_monitor_par(vo); p->sws = mp_sws_alloc(vo); p->sws->log = vo->log; mp_sws_enable_cmdline_opts(p->sws, vo->global); return 0;
err: uninit(vo); return -1;}
static int query_format(struct vo *vo, int format){ struct priv *p = vo->priv; return mp_sws_supports_formats(p->sws, p->imgfmt, format) ? 1 : 0;}
static int control(struct vo *vo, uint32_t request, void *arg){ switch (request) { case VOCTRL_SET_PANSCAN: if (vo->config_ok) reconfig(vo, vo->params); return VO_TRUE; }
int events = 0; int ret = vo_drm_control(vo, &events, request, arg); vo_event(vo, events); return ret;}
const struct vo_driver video_out_drm = { .name = "drm", .description = "Direct Rendering Manager (software scaling)", .preinit = preinit, .query_format = query_format, .reconfig = reconfig, .control = control, .draw_frame = draw_frame, .flip_page = flip_page, .get_vsync = get_vsync, .uninit = uninit, .wait_events = vo_drm_wait_events, .wakeup = vo_drm_wakeup, .priv_size = sizeof(struct priv),};