static struct mp_image *filter(struct vf_instance *vf, struct mp_image *mpi) { int e_x = vf->priv->exp_x, e_y = vf->priv->exp_y; int e_w = vf->priv->exp_w, e_h = vf->priv->exp_h; if (e_x == 0 && e_y == 0 && e_w == mpi->w && e_h == mpi->h) return mpi; struct mp_image *dmpi = vf_alloc_out_image(vf); mp_image_copy_attributes(dmpi, mpi); struct mp_image cropped = *dmpi; mp_image_crop(&cropped, e_x, e_y, e_x + mpi->w, e_y + mpi->h); mp_image_copy(&cropped, mpi); int e_x2 = e_x + MP_ALIGN_DOWN(mpi->w, mpi->fmt.align_x); int e_y2 = e_y + MP_ALIGN_DOWN(mpi->h, mpi->fmt.align_y); // top border (over the full width) mp_image_clear(dmpi, 0, 0, e_w, e_y); // bottom border (over the full width) mp_image_clear(dmpi, 0, e_y2, e_w, e_h); // left mp_image_clear(dmpi, 0, e_y, e_x, e_y2); // right mp_image_clear(dmpi, e_x2, e_y, e_w, e_y2); talloc_free(mpi); return dmpi; }
static bool allocate_xvimage(struct vo *vo, int foo) { struct xvctx *ctx = vo->priv; struct vo_x11_state *x11 = vo->x11; // align it for faster OSD rendering (draw_bmp.c swscale usage) int aligned_w = FFALIGN(ctx->image_width, 32); #if HAVE_SHM if (x11->display_is_local && XShmQueryExtension(x11->display)) { ctx->Shmem_Flag = 1; x11->ShmCompletionEvent = XShmGetEventBase(x11->display) + ShmCompletion; } else { ctx->Shmem_Flag = 0; MP_INFO(vo, "Shared memory not supported\nReverting to normal Xv.\n"); } if (ctx->Shmem_Flag) { ctx->xvimage[foo] = (XvImage *) XvShmCreateImage(x11->display, ctx->xv_port, ctx->xv_format, NULL, aligned_w, ctx->image_height, &ctx->Shminfo[foo]); if (!ctx->xvimage[foo]) return false; ctx->Shminfo[foo].shmid = shmget(IPC_PRIVATE, ctx->xvimage[foo]->data_size, IPC_CREAT | 0777); ctx->Shminfo[foo].shmaddr = (char *) shmat(ctx->Shminfo[foo].shmid, 0, 0); if (ctx->Shminfo[foo].shmaddr == (void *)-1) return false; ctx->Shminfo[foo].readOnly = False; ctx->xvimage[foo]->data = ctx->Shminfo[foo].shmaddr; XShmAttach(x11->display, &ctx->Shminfo[foo]); XSync(x11->display, False); shmctl(ctx->Shminfo[foo].shmid, IPC_RMID, 0); } else #endif { ctx->xvimage[foo] = (XvImage *) XvCreateImage(x11->display, ctx->xv_port, ctx->xv_format, NULL, aligned_w, ctx->image_height); if (!ctx->xvimage[foo]) return false; ctx->xvimage[foo]->data = av_malloc(ctx->xvimage[foo]->data_size); if (!ctx->xvimage[foo]->data) return false; XSync(x11->display, False); } struct mp_image img = get_xv_buffer(vo, foo); img.w = aligned_w; mp_image_clear(&img, 0, 0, img.w, img.h); return true; }
// Note: redraw_frame() can call this with NULL. static void draw_image(struct vo *vo, mp_image_t *mpi) { struct xvctx *ctx = vo->priv; wait_for_completion(vo, ctx->num_buffers - 1); struct mp_image xv_buffer = get_xv_buffer(vo, ctx->current_buf); if (mpi) { mp_image_copy(&xv_buffer, mpi); } else { mp_image_clear(&xv_buffer, 0, 0, xv_buffer.w, xv_buffer.h); } mp_image_setrefp(&ctx->original_image, mpi); }
// Note: REDRAW_FRAME can call this with NULL. static void draw_image(struct vo *vo, mp_image_t *mpi) { struct xvctx *ctx = vo->priv; wait_for_completion(vo, ctx->num_buffers - 1); struct mp_image xv_buffer = get_xv_buffer(vo, ctx->current_buf); if (mpi) { mp_image_copy(&xv_buffer, mpi); } else { mp_image_clear(&xv_buffer, 0, 0, xv_buffer.w, xv_buffer.h); } struct mp_osd_res res = osd_res_from_image_params(vo->params); osd_draw_on_image(vo->osd, res, mpi ? mpi->pts : 0, 0, &xv_buffer); if (mpi != ctx->original_image) { talloc_free(ctx->original_image); ctx->original_image = mpi; } }
static void draw_osd(struct vo *vo, struct osd_state *osd) { struct xvctx *ctx = vo->priv; struct mp_image img = get_xv_buffer(vo, ctx->current_buf); struct mp_osd_res res = { .w = ctx->image_width, .h = ctx->image_height, .display_par = 1.0 / vo->aspdat.par, }; osd_draw_on_image(osd, res, osd->vo_pts, 0, &img); } static void wait_for_completion(struct vo *vo, int max_outstanding) { #if HAVE_SHM struct xvctx *ctx = vo->priv; struct vo_x11_state *x11 = vo->x11; if (ctx->Shmem_Flag) { while (x11->ShmCompletionWaitCount > max_outstanding) { if (!ctx->Shm_Warned_Slow) { MP_WARN(vo, "X11 can't keep up! Waiting" " for XShm completion events...\n"); ctx->Shm_Warned_Slow = 1; } mp_sleep_us(1000); vo_x11_check_events(vo); } } #endif } static void flip_page(struct vo *vo) { struct xvctx *ctx = vo->priv; put_xvimage(vo, ctx->xvimage[ctx->current_buf]); /* remember the currently visible buffer */ ctx->current_buf = (ctx->current_buf + 1) % ctx->num_buffers; if (!ctx->Shmem_Flag) XSync(vo->x11->display, False); } static mp_image_t *get_screenshot(struct vo *vo) { struct xvctx *ctx = vo->priv; if (!ctx->original_image) return NULL; return mp_image_new_ref(ctx->original_image); } // Note: redraw_frame() can call this with NULL. static void draw_image(struct vo *vo, mp_image_t *mpi) { struct xvctx *ctx = vo->priv; wait_for_completion(vo, ctx->num_buffers - 1); struct mp_image xv_buffer = get_xv_buffer(vo, ctx->current_buf); if (mpi) { mp_image_copy(&xv_buffer, mpi); } else { mp_image_clear(&xv_buffer, 0, 0, xv_buffer.w, xv_buffer.h); } mp_image_setrefp(&ctx->original_image, mpi); } static int redraw_frame(struct vo *vo) { struct xvctx *ctx = vo->priv; draw_image(vo, ctx->original_image); return true; } static int query_format(struct vo *vo, uint32_t format) { struct xvctx *ctx = vo->priv; uint32_t i; int flag = VFCAP_CSP_SUPPORTED | VFCAP_CSP_SUPPORTED_BY_HW; int fourcc = find_xv_format(format); if (fourcc) { for (i = 0; i < ctx->formats; i++) { if (ctx->fo[i].id == fourcc) return flag; } } return 0; } static void uninit(struct vo *vo) { struct xvctx *ctx = vo->priv; int i; talloc_free(ctx->original_image); if (ctx->ai) XvFreeAdaptorInfo(ctx->ai); ctx->ai = NULL; if (ctx->fo) { XFree(ctx->fo); ctx->fo = NULL; } for (i = 0; i < ctx->num_buffers; i++) deallocate_xvimage(vo, i); // uninit() shouldn't get called unless initialization went past vo_init() vo_x11_uninit(vo); } static int preinit(struct vo *vo) { XvPortID xv_p; int busy_ports = 0; unsigned int i; struct xvctx *ctx = vo->priv; int xv_adaptor = ctx->cfg_xv_adaptor; if (!vo_x11_init(vo)) return -1; struct vo_x11_state *x11 = vo->x11; /* check for Xvideo extension */ unsigned int ver, rel, req, ev, err; if (Success != XvQueryExtension(x11->display, &ver, &rel, &req, &ev, &err)) { MP_ERR(vo, "Xv not supported by this X11 version/driver\n"); goto error; } /* check for Xvideo support */ if (Success != XvQueryAdaptors(x11->display, DefaultRootWindow(x11->display), &ctx->adaptors, &ctx->ai)) { MP_ERR(vo, "XvQueryAdaptors failed.\n"); goto error; } /* check adaptors */ if (ctx->xv_port) { int port_found; for (port_found = 0, i = 0; !port_found && i < ctx->adaptors; i++) { if ((ctx->ai[i].type & XvInputMask) && (ctx->ai[i].type & XvImageMask)) { for (xv_p = ctx->ai[i].base_id; xv_p < ctx->ai[i].base_id + ctx->ai[i].num_ports; ++xv_p) { if (xv_p == ctx->xv_port) { port_found = 1; break; } } } } if (port_found) { if (XvGrabPort(x11->display, ctx->xv_port, CurrentTime)) ctx->xv_port = 0; } else { MP_WARN(vo, "Invalid port parameter, overriding with port 0.\n"); ctx->xv_port = 0; } } for (i = 0; i < ctx->adaptors && ctx->xv_port == 0; i++) { /* check if adaptor number has been specified */ if (xv_adaptor != -1 && xv_adaptor != i) continue; if ((ctx->ai[i].type & XvInputMask) && (ctx->ai[i].type & XvImageMask)) { for (xv_p = ctx->ai[i].base_id; xv_p < ctx->ai[i].base_id + ctx->ai[i].num_ports; ++xv_p) if (!XvGrabPort(x11->display, xv_p, CurrentTime)) { ctx->xv_port = xv_p; MP_VERBOSE(vo, "Using Xv Adapter #%d (%s)\n", i, ctx->ai[i].name); break; } else { MP_WARN(vo, "Could not grab port %i.\n", (int) xv_p); ++busy_ports; } } } if (!ctx->xv_port) { if (busy_ports) MP_ERR(vo, "Could not find free Xvideo port - maybe another process is already\n"\ "using it. Close all video applications, and try again. If that does\n"\ "not help, see 'mpv -vo help' for other (non-xv) video out drivers.\n"); else MP_ERR(vo, "It seems there is no Xvideo support for your video card available.\n"\ "Run 'xvinfo' to verify its Xv support and read\n"\ "DOCS/HTML/en/video.html#xv!\n"\ "See 'mpv -vo help' for other (non-xv) video out drivers.\n"\ "Try -vo x11.\n"); goto error; } if (!xv_init_colorkey(vo)) { goto error; // bail out, colorkey setup failed } xv_enable_vsync(vo); xv_get_max_img_dim(vo, &ctx->max_width, &ctx->max_height); ctx->fo = XvListImageFormats(x11->display, ctx->xv_port, (int *) &ctx->formats); return 0; error: uninit(vo); // free resources return -1; }
static void VS_CC infiltInit(VSMap *in, VSMap *out, void **instanceData, VSNode *node, VSCore *core, const VSAPI *vsapi) { struct vf_instance *vf = *instanceData; struct vf_priv_s *p = vf->priv; // The number of frames of our input node is obviously unknown. The user // could for example seek any time, randomly "ending" the clip. // This specific value was suggested by the VapourSynth developer. int enough_for_everyone = INT_MAX / 16; // Note: this is called from createFilter, so no need for locking. VSVideoInfo fmt = { .format = p->vsapi->getFormatPreset(mp_to_vs(p->fmt_in.imgfmt), p->vscore), .width = p->fmt_in.w, .height = p->fmt_in.h, .numFrames = enough_for_everyone, }; if (!fmt.format) { p->vsapi->setError(out, "Unsupported input format.\n"); return; } p->vsapi->setVideoInfo(&fmt, 1, node); p->in_node_active = true; } static const VSFrameRef *VS_CC infiltGetFrame(int frameno, int activationReason, void **instanceData, void **frameData, VSFrameContext *frameCtx, VSCore *core, const VSAPI *vsapi) { struct vf_instance *vf = *instanceData; struct vf_priv_s *p = vf->priv; VSFrameRef *ret = NULL; pthread_mutex_lock(&p->lock); MP_TRACE(vf, "VS asking for frame %d (at %d)\n", frameno, p->in_frameno); while (1) { if (p->shutdown) { p->vsapi->setFilterError("EOF or filter reinit/uninit", frameCtx); MP_DBG(vf, "returning error on EOF/reset\n"); break; } if (p->initializing) { MP_WARN(vf, "Frame requested during init! This is unsupported.\n" "Returning black dummy frame with 0 duration.\n"); ret = alloc_vs_frame(p, &vf->fmt_in); if (!ret) { p->vsapi->setFilterError("Could not allocate VS frame", frameCtx); break; } struct mp_image vsframe = map_vs_frame(p, ret, true); mp_image_clear(&vsframe, 0, 0, vf->fmt_in.w, vf->fmt_in.h); struct mp_image dummy = {0}; mp_image_set_params(&dummy, &vf->fmt_in); set_vs_frame_props(p, ret, &dummy, 0, 1); break; } if (frameno < p->in_frameno) { char msg[180]; snprintf(msg, sizeof(msg), "Frame %d requested, but only have frames starting from %d. " "Try increasing the buffered-frames suboption.", frameno, p->in_frameno); MP_FATAL(vf, "%s\n", msg); p->vsapi->setFilterError(msg, frameCtx); break; } if (frameno >= p->in_frameno + MP_TALLOC_AVAIL(p->buffered)) { // Too far in the future. Remove frames, so that the main thread can // queue new frames. if (p->num_buffered) { drain_oldest_buffered_frame(p); pthread_cond_broadcast(&p->wakeup); if (vf->chain->wakeup_callback) vf->chain->wakeup_callback(vf->chain->wakeup_callback_ctx); continue; } } if (frameno >= p->in_frameno + p->num_buffered) { // If we think EOF was reached, don't wait for new input, and assume // the VS filter has reached EOF. if (p->eof) { p->shutdown = true; continue; } } if (frameno < p->in_frameno + p->num_buffered) { struct mp_image *img = p->buffered[frameno - p->in_frameno]; ret = alloc_vs_frame(p, &img->params); if (!ret) { p->vsapi->setFilterError("Could not allocate VS frame", frameCtx); break; } struct mp_image vsframe = map_vs_frame(p, ret, true); mp_image_copy(&vsframe, img); int res = 1e6; int dur = img->pts * res + 0.5; set_vs_frame_props(p, ret, img, dur, res); break; } pthread_cond_wait(&p->wakeup, &p->lock); } pthread_cond_broadcast(&p->wakeup); pthread_mutex_unlock(&p->lock); return ret; } static void VS_CC infiltFree(void *instanceData, VSCore *core, const VSAPI *vsapi) { struct vf_instance *vf = instanceData; struct vf_priv_s *p = vf->priv; pthread_mutex_lock(&p->lock); p->in_node_active = false; pthread_cond_broadcast(&p->wakeup); pthread_mutex_unlock(&p->lock); } // number of getAsyncFrame calls in progress // must be called with p->lock held static int num_requested(struct vf_priv_s *p) { int r = 0; for (int n = 0; n < p->max_requests; n++) r += p->requested[n] == &dummy_img; return r; }
static bool resize(struct vo *vo) { struct priv *p = vo->priv; struct vo_x11_state *x11 = vo->x11; for (int i = 0; i < 2; i++) freeMyXImage(p, i); vo_get_src_dst_rects(vo, &p->src, &p->dst, &p->osd); p->src_w = p->src.x1 - p->src.x0; p->src_h = p->src.y1 - p->src.y0; p->dst_w = p->dst.x1 - p->dst.x0; p->dst_h = p->dst.y1 - p->dst.y0; // p->osd contains the parameters assuming OSD rendering in window // coordinates, but OSD can only be rendered in the intersection // between window and video rectangle (i.e. not into panscan borders). p->osd.w = p->dst_w; p->osd.h = p->dst_h; p->osd.mt = MPMIN(0, p->osd.mt); p->osd.mb = MPMIN(0, p->osd.mb); p->osd.mr = MPMIN(0, p->osd.mr); p->osd.ml = MPMIN(0, p->osd.ml); mp_input_set_mouse_transform(vo->input_ctx, &p->dst, NULL); p->image_width = (p->dst_w + 7) & (~7); p->image_height = p->dst_h; for (int i = 0; i < 2; i++) { if (!getMyXImage(p, i)) return -1; } const struct fmt_entry *fmte = mp_to_x_fmt; while (fmte->mpfmt) { if (fmte->depth == p->myximage[0]->bits_per_pixel && fmte->byte_order == p->myximage[0]->byte_order && fmte->red_mask == p->myximage[0]->red_mask && fmte->green_mask == p->myximage[0]->green_mask && fmte->blue_mask == p->myximage[0]->blue_mask) break; fmte++; } if (!fmte->mpfmt) { MP_ERR(vo, "X server image format not supported, use another VO.\n"); return -1; } mp_sws_set_from_cmdline(p->sws, vo->opts->sws_opts); p->sws->dst = (struct mp_image_params) { .imgfmt = fmte->mpfmt, .w = p->dst_w, .h = p->dst_h, .p_w = 1, .p_h = 1, }; mp_image_params_guess_csp(&p->sws->dst); if (mp_sws_reinit(p->sws) < 0) return false; XFillRectangle(x11->display, x11->window, p->gc, 0, 0, vo->dwidth, vo->dheight); vo->want_redraw = true; return true; } static void Display_Image(struct priv *p, XImage *myximage) { struct vo *vo = p->vo; XImage *x_image = p->myximage[p->current_buf]; #if HAVE_SHM && HAVE_XEXT if (p->Shmem_Flag) { XShmPutImage(vo->x11->display, vo->x11->window, p->gc, x_image, 0, 0, p->dst.x0, p->dst.y0, p->dst_w, p->dst_h, True); vo->x11->ShmCompletionWaitCount++; } else #endif { XPutImage(vo->x11->display, vo->x11->window, p->gc, x_image, 0, 0, p->dst.x0, p->dst.y0, p->dst_w, p->dst_h); } } static struct mp_image get_x_buffer(struct priv *p, int buf_index) { struct mp_image img = {0}; mp_image_set_params(&img, &p->sws->dst); img.planes[0] = p->myximage[buf_index]->data; img.stride[0] = p->image_width * ((p->myximage[buf_index]->bits_per_pixel + 7) / 8); return img; } static void wait_for_completion(struct vo *vo, int max_outstanding) { #if HAVE_SHM && HAVE_XEXT struct priv *ctx = vo->priv; struct vo_x11_state *x11 = vo->x11; if (ctx->Shmem_Flag) { while (x11->ShmCompletionWaitCount > max_outstanding) { if (!ctx->Shm_Warned_Slow) { MP_WARN(vo, "can't keep up! Waiting" " for XShm completion events...\n"); ctx->Shm_Warned_Slow = 1; } mp_sleep_us(1000); vo_x11_check_events(vo); } } #endif } static void flip_page(struct vo *vo) { struct priv *p = vo->priv; Display_Image(p, p->myximage[p->current_buf]); p->current_buf = (p->current_buf + 1) % 2; } // Note: REDRAW_FRAME can call this with NULL. static void draw_image(struct vo *vo, mp_image_t *mpi) { struct priv *p = vo->priv; wait_for_completion(vo, 1); struct mp_image img = get_x_buffer(p, p->current_buf); if (mpi) { struct mp_image src = *mpi; struct mp_rect src_rc = p->src; src_rc.x0 = MP_ALIGN_DOWN(src_rc.x0, src.fmt.align_x); src_rc.y0 = MP_ALIGN_DOWN(src_rc.y0, src.fmt.align_y); mp_image_crop_rc(&src, src_rc); mp_sws_scale(p->sws, &img, &src); } else { mp_image_clear(&img, 0, 0, img.w, img.h); } osd_draw_on_image(vo->osd, p->osd, mpi ? mpi->pts : 0, 0, &img); if (mpi != p->original_image) { talloc_free(p->original_image); p->original_image = mpi; } } static int query_format(struct vo *vo, int format) { if (sws_isSupportedInput(imgfmt2pixfmt(format))) return 1; return 0; } static void uninit(struct vo *vo) { struct priv *p = vo->priv; if (p->myximage[0]) freeMyXImage(p, 0); if (p->myximage[1]) freeMyXImage(p, 1); if (p->gc) XFreeGC(vo->x11->display, p->gc); talloc_free(p->original_image); vo_x11_uninit(vo); } static int preinit(struct vo *vo) { struct priv *p = vo->priv; p->vo = vo; p->sws = mp_sws_alloc(vo); if (!vo_x11_init(vo)) goto error; struct vo_x11_state *x11 = vo->x11; XWindowAttributes attribs; XGetWindowAttributes(x11->display, x11->rootwin, &attribs); p->depth = attribs.depth; if (!XMatchVisualInfo(x11->display, x11->screen, p->depth, TrueColor, &p->vinfo)) goto error; MP_VERBOSE(vo, "selected visual: %d\n", (int)p->vinfo.visualid); if (!vo_x11_create_vo_window(vo, &p->vinfo, "x11")) goto error; p->gc = XCreateGC(x11->display, x11->window, 0, NULL); MP_WARN(vo, "Warning: this legacy VO has bad performance. Consider fixing " "your graphics drivers, or not forcing the x11 VO.\n"); return 0; error: uninit(vo); return -1; } static int control(struct vo *vo, uint32_t request, void *data) { struct priv *p = vo->priv; switch (request) { case VOCTRL_SET_PANSCAN: if (vo->config_ok) resize(vo); return VO_TRUE; case VOCTRL_REDRAW_FRAME: draw_image(vo, p->original_image); return true; } int events = 0; int r = vo_x11_control(vo, &events, request, data); if (vo->config_ok && (events & (VO_EVENT_EXPOSE | VO_EVENT_RESIZE))) resize(vo); vo_event(vo, events); return r; } const struct vo_driver video_out_x11 = { .description = "X11 (slow, old crap)", .name = "x11", .priv_size = sizeof(struct priv), .preinit = preinit, .query_format = query_format, .reconfig = reconfig, .control = control, .draw_image = draw_image, .flip_page = flip_page, .wakeup = vo_x11_wakeup, .wait_events = vo_x11_wait_events, .uninit = uninit, };