static void filter_video(struct MPContext *mpctx, struct mp_image *frame, bool reconfig_ok) { struct dec_video *d_video = mpctx->d_video; struct mp_image_params params; mp_image_params_from_image(¶ms, frame); if (!mp_image_params_equals(&d_video->decoder_output, ¶ms) || d_video->vfilter->initialized < 1) { // In case we want to wait until filter chain is drained if (!reconfig_ok) { talloc_free(d_video->waiting_decoded_mpi); d_video->waiting_decoded_mpi = frame; return; } reconfig_video(mpctx, ¶ms, false); if (d_video->vfilter->initialized > 0) init_filter_params(mpctx); } if (d_video->vfilter->initialized < 1) { talloc_free(frame); return; } mp_image_set_params(frame, &d_video->vf_input); // force csp/aspect overrides vf_filter_frame(d_video->vfilter, frame); filter_output_queued_frame(mpctx); }
static void filter_video(struct MPContext *mpctx, struct mp_image *frame) { struct sh_video *sh_video = mpctx->sh_video; init_filter_params(mpctx); frame->pts = sh_video->pts; mp_image_set_params(frame, sh_video->vf_input); vf_filter_frame(sh_video->vfilter, frame); filter_output_queued_frame(mpctx); }
static void filter_video(struct MPContext *mpctx, struct mp_image *frame) { struct dec_video *d_video = mpctx->d_video; init_filter_params(mpctx); frame->pts = d_video->pts; mp_image_set_params(frame, &d_video->vf_input); // force csp/aspect overrides vf_filter_frame(d_video->vfilter, frame); filter_output_queued_frame(mpctx); }
// Make mpi point to buffer, assuming MMAL_ENCODING_I420. // buffer can be NULL. // Return the required buffer space. static size_t layout_buffer(struct mp_image *mpi, MMAL_BUFFER_HEADER_T *buffer, struct mp_image_params *params) { assert(params->imgfmt == IMGFMT_420P); mp_image_set_params(mpi, params); int w = MP_ALIGN_UP(params->w, ALIGN_W); int h = MP_ALIGN_UP(params->h, ALIGN_H); uint8_t *cur = buffer ? buffer->data : NULL; size_t size = 0; for (int i = 0; i < 3; i++) { int div = i ? 2 : 1; mpi->planes[i] = cur; mpi->stride[i] = w / div; size_t plane_size = h / div * mpi->stride[i]; if (cur) cur += plane_size; size += plane_size; } return size; }
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 int recreate_video_proc(struct vf_instance *vf) { struct vf_priv_s *p = vf->priv; HRESULT hr; destroy_video_proc(vf); D3D11_VIDEO_PROCESSOR_CONTENT_DESC vpdesc = { .InputFrameFormat = p->d3d_frame_format, .InputWidth = p->c_w, .InputHeight = p->c_h, .OutputWidth = p->params.w, .OutputHeight = p->params.h, }; hr = ID3D11VideoDevice_CreateVideoProcessorEnumerator(p->video_dev, &vpdesc, &p->vp_enum); if (FAILED(hr)) goto fail; D3D11_VIDEO_PROCESSOR_CAPS caps; hr = ID3D11VideoProcessorEnumerator_GetVideoProcessorCaps(p->vp_enum, &caps); if (FAILED(hr)) goto fail; MP_VERBOSE(vf, "Found %d rate conversion caps. Looking for caps=0x%x.\n", (int)caps.RateConversionCapsCount, p->mode); int rindex = -1; for (int n = 0; n < caps.RateConversionCapsCount; n++) { D3D11_VIDEO_PROCESSOR_RATE_CONVERSION_CAPS rcaps; hr = ID3D11VideoProcessorEnumerator_GetVideoProcessorRateConversionCaps (p->vp_enum, n, &rcaps); if (FAILED(hr)) goto fail; MP_VERBOSE(vf, " - %d: 0x%08x\n", n, (unsigned)rcaps.ProcessorCaps); if (rcaps.ProcessorCaps & p->mode) { MP_VERBOSE(vf, " (matching)\n"); if (rindex < 0) rindex = n; } } if (rindex < 0) { MP_WARN(vf, "No fitting video processor found, picking #0.\n"); rindex = 0; } // TOOD: so, how do we select which rate conversion mode the processor uses? hr = ID3D11VideoDevice_CreateVideoProcessor(p->video_dev, p->vp_enum, rindex, &p->video_proc); if (FAILED(hr)) { MP_ERR(vf, "Failed to create D3D11 video processor.\n"); goto fail; } // Note: libavcodec does not support cropping left/top with hwaccel. RECT src_rc = { .right = p->params.w, .bottom = p->params.h, }; ID3D11VideoContext_VideoProcessorSetStreamSourceRect(p->video_ctx, p->video_proc, 0, TRUE, &src_rc); // This is supposed to stop drivers from f*****g up the video quality. ID3D11VideoContext_VideoProcessorSetStreamAutoProcessingMode(p->video_ctx, p->video_proc, 0, FALSE); ID3D11VideoContext_VideoProcessorSetStreamOutputRate(p->video_ctx, p->video_proc, 0, D3D11_VIDEO_PROCESSOR_OUTPUT_RATE_NORMAL, FALSE, 0); D3D11_VIDEO_PROCESSOR_COLOR_SPACE csp = { .YCbCr_Matrix = p->params.color.space != MP_CSP_BT_601, .Nominal_Range = p->params.color.levels == MP_CSP_LEVELS_TV ? 1 : 2, }; ID3D11VideoContext_VideoProcessorSetStreamColorSpace(p->video_ctx, p->video_proc, 0, &csp); if (p->out_rgb) { if (p->params.color.space != MP_CSP_BT_601 && p->params.color.space != MP_CSP_BT_709) { MP_WARN(vf, "Unsupported video colorspace (%s/%s). Consider " "disabling hardware decoding, or using " "--hwdec=d3d11va-copy to get correct output.\n", m_opt_choice_str(mp_csp_names, p->params.color.space), m_opt_choice_str(mp_csp_levels_names, p->params.color.levels)); } } else { ID3D11VideoContext_VideoProcessorSetOutputColorSpace(p->video_ctx, p->video_proc, &csp); } return 0; fail: destroy_video_proc(vf); return -1; } static int render(struct vf_instance *vf) { struct vf_priv_s *p = vf->priv; int res = -1; HRESULT hr; ID3D11VideoProcessorInputView *in_view = NULL; ID3D11VideoProcessorOutputView *out_view = NULL; struct mp_image *in = NULL, *out = NULL; out = mp_image_pool_get(p->pool, p->out_params.imgfmt, p->params.w, p->params.h); if (!out) goto cleanup; ID3D11Texture2D *d3d_out_tex = (void *)out->planes[1]; in = mp_refqueue_get(p->queue, 0); if (!in) goto cleanup; ID3D11Texture2D *d3d_tex = (void *)in->planes[1]; int d3d_subindex = (intptr_t)in->planes[2]; mp_image_copy_attributes(out, in); D3D11_VIDEO_FRAME_FORMAT d3d_frame_format; if (!mp_refqueue_should_deint(p->queue)) { d3d_frame_format = D3D11_VIDEO_FRAME_FORMAT_PROGRESSIVE; } else if (mp_refqueue_top_field_first(p->queue)) { d3d_frame_format = D3D11_VIDEO_FRAME_FORMAT_INTERLACED_TOP_FIELD_FIRST; } else { d3d_frame_format = D3D11_VIDEO_FRAME_FORMAT_INTERLACED_BOTTOM_FIELD_FIRST; } D3D11_TEXTURE2D_DESC texdesc; ID3D11Texture2D_GetDesc(d3d_tex, &texdesc); if (!p->video_proc || p->c_w != texdesc.Width || p->c_h != texdesc.Height || p->d3d_frame_format != d3d_frame_format) { p->c_w = texdesc.Width; p->c_h = texdesc.Height; p->d3d_frame_format = d3d_frame_format; if (recreate_video_proc(vf) < 0) goto cleanup; } if (!mp_refqueue_should_deint(p->queue)) { d3d_frame_format = D3D11_VIDEO_FRAME_FORMAT_PROGRESSIVE; } else if (mp_refqueue_is_top_field(p->queue)) { d3d_frame_format = D3D11_VIDEO_FRAME_FORMAT_INTERLACED_TOP_FIELD_FIRST; } else { d3d_frame_format = D3D11_VIDEO_FRAME_FORMAT_INTERLACED_BOTTOM_FIELD_FIRST; } ID3D11VideoContext_VideoProcessorSetStreamFrameFormat(p->video_ctx, p->video_proc, 0, d3d_frame_format); D3D11_VIDEO_PROCESSOR_INPUT_VIEW_DESC indesc = { .ViewDimension = D3D11_VPIV_DIMENSION_TEXTURE2D, .Texture2D = { .ArraySlice = d3d_subindex, }, }; hr = ID3D11VideoDevice_CreateVideoProcessorInputView(p->video_dev, (ID3D11Resource *)d3d_tex, p->vp_enum, &indesc, &in_view); if (FAILED(hr)) { MP_ERR(vf, "Could not create ID3D11VideoProcessorInputView\n"); goto cleanup; } D3D11_VIDEO_PROCESSOR_OUTPUT_VIEW_DESC outdesc = { .ViewDimension = D3D11_VPOV_DIMENSION_TEXTURE2D, }; hr = ID3D11VideoDevice_CreateVideoProcessorOutputView(p->video_dev, (ID3D11Resource *)d3d_out_tex, p->vp_enum, &outdesc, &out_view); if (FAILED(hr)) goto cleanup; D3D11_VIDEO_PROCESSOR_STREAM stream = { .Enable = TRUE, .pInputSurface = in_view, }; int frame = mp_refqueue_is_second_field(p->queue); hr = ID3D11VideoContext_VideoProcessorBlt(p->video_ctx, p->video_proc, out_view, frame, 1, &stream); if (FAILED(hr)) { MP_ERR(vf, "VideoProcessorBlt failed.\n"); goto cleanup; } res = 0; cleanup: if (in_view) ID3D11VideoProcessorInputView_Release(in_view); if (out_view) ID3D11VideoProcessorOutputView_Release(out_view); if (res >= 0) { vf_add_output_frame(vf, out); } else { talloc_free(out); } mp_refqueue_next_field(p->queue); return res; } static int filter_out(struct vf_instance *vf) { struct vf_priv_s *p = vf->priv; if (!mp_refqueue_has_output(p->queue)) return 0; // no filtering if (!mp_refqueue_should_deint(p->queue) && !p->require_filtering) { struct mp_image *in = mp_image_new_ref(mp_refqueue_get(p->queue, 0)); if (!in) return -1; mp_image_set_params(in, &p->out_params); vf_add_output_frame(vf, in); mp_refqueue_next(p->queue); return 0; } return render(vf); } static int reconfig(struct vf_instance *vf, struct mp_image_params *in, struct mp_image_params *out) { struct vf_priv_s *p = vf->priv; flush_frames(vf); talloc_free(p->pool); p->pool = NULL; destroy_video_proc(vf); *out = *in; if (vf_next_query_format(vf, IMGFMT_D3D11VA) || vf_next_query_format(vf, IMGFMT_D3D11NV12)) { out->imgfmt = vf_next_query_format(vf, IMGFMT_D3D11VA) ? IMGFMT_D3D11VA : IMGFMT_D3D11NV12; out->hw_subfmt = IMGFMT_NV12; p->out_format = DXGI_FORMAT_NV12; p->out_shared = false; p->out_rgb = false; } else { out->imgfmt = IMGFMT_D3D11RGB; out->hw_subfmt = IMGFMT_RGB0; p->out_format = DXGI_FORMAT_B8G8R8A8_UNORM; p->out_shared = true; p->out_rgb = true; } p->require_filtering = in->hw_subfmt != out->hw_subfmt; p->params = *in; p->out_params = *out; p->pool = mp_image_pool_new(20); mp_image_pool_set_allocator(p->pool, alloc_pool, vf); mp_image_pool_set_lru(p->pool); return 0; } static void uninit(struct vf_instance *vf) { struct vf_priv_s *p = vf->priv; destroy_video_proc(vf); flush_frames(vf); mp_refqueue_free(p->queue); talloc_free(p->pool); if (p->video_ctx) ID3D11VideoContext_Release(p->video_ctx); if (p->video_dev) ID3D11VideoDevice_Release(p->video_dev); if (p->device_ctx) ID3D11DeviceContext_Release(p->device_ctx); if (p->vo_dev) ID3D11Device_Release(p->vo_dev); } static int query_format(struct vf_instance *vf, unsigned int imgfmt) { if (imgfmt == IMGFMT_D3D11VA || imgfmt == IMGFMT_D3D11NV12 || imgfmt == IMGFMT_D3D11RGB) { return vf_next_query_format(vf, IMGFMT_D3D11VA) || vf_next_query_format(vf, IMGFMT_D3D11NV12) || vf_next_query_format(vf, IMGFMT_D3D11RGB); } return 0; } static bool test_conversion(int in, int out) { return (in == IMGFMT_D3D11VA || in == IMGFMT_D3D11NV12 || in == IMGFMT_D3D11RGB) && (out == IMGFMT_D3D11VA || out == IMGFMT_D3D11NV12 || out == IMGFMT_D3D11RGB); } static int control(struct vf_instance *vf, int request, void* data) { struct vf_priv_s *p = vf->priv; switch (request){ case VFCTRL_GET_DEINTERLACE: *(int*)data = !!p->deint_enabled; return true; case VFCTRL_SET_DEINTERLACE: p->deint_enabled = !!*(int*)data; return true; case VFCTRL_SEEK_RESET: flush_frames(vf); return true; default: return CONTROL_UNKNOWN; } } static int vf_open(vf_instance_t *vf) { struct vf_priv_s *p = vf->priv; vf->reconfig = reconfig; vf->filter_ext = filter_ext; vf->filter_out = filter_out; vf->query_format = query_format; vf->uninit = uninit; vf->control = control; p->queue = mp_refqueue_alloc(); p->vo_dev = hwdec_devices_load(vf->hwdec_devs, HWDEC_D3D11VA); if (!p->vo_dev) return 0; ID3D11Device_AddRef(p->vo_dev); HRESULT hr; hr = ID3D11Device_QueryInterface(p->vo_dev, &IID_ID3D11VideoDevice, (void **)&p->video_dev); if (FAILED(hr)) goto fail; ID3D11Device_GetImmediateContext(p->vo_dev, &p->device_ctx); if (!p->device_ctx) goto fail; hr = ID3D11DeviceContext_QueryInterface(p->device_ctx, &IID_ID3D11VideoContext, (void **)&p->video_ctx); if (FAILED(hr)) goto fail; return 1; fail: uninit(vf); return 0; } #define OPT_BASE_STRUCT struct vf_priv_s static const m_option_t vf_opts_fields[] = { OPT_FLAG("deint", deint_enabled, 0), OPT_FLAG("interlaced-only", interlaced_only, 0), OPT_CHOICE("mode", mode, 0, ({"blend", D3D11_VIDEO_PROCESSOR_PROCESSOR_CAPS_DEINTERLACE_BLEND}, {"bob", D3D11_VIDEO_PROCESSOR_PROCESSOR_CAPS_DEINTERLACE_BOB}, {"adaptive", D3D11_VIDEO_PROCESSOR_PROCESSOR_CAPS_DEINTERLACE_ADAPTIVE}, {"mocomp", D3D11_VIDEO_PROCESSOR_PROCESSOR_CAPS_DEINTERLACE_MOTION_COMPENSATION},
static int map_image(struct gl_hwdec *hw, struct mp_image *hw_image, GLuint *out_textures) { struct priv *p = hw->priv; GL *gl = hw->gl; VAStatus status; VAImage *va_image = &p->current_image; unref_image(hw); mp_image_setrefp(&p->current_ref, hw_image); va_lock(p->ctx); status = vaDeriveImage(p->display, va_surface_id(hw_image), va_image); if (!CHECK_VA_STATUS(p, "vaDeriveImage()")) goto err; int mpfmt = va_fourcc_to_imgfmt(va_image->format.fourcc); if (mpfmt != IMGFMT_NV12 && mpfmt != IMGFMT_420P) { MP_FATAL(p, "unsupported VA image format %s\n", VA_STR_FOURCC(va_image->format.fourcc)); goto err; } if (!hw->converted_imgfmt) { MP_VERBOSE(p, "format: %s %s\n", VA_STR_FOURCC(va_image->format.fourcc), mp_imgfmt_to_name(mpfmt)); hw->converted_imgfmt = mpfmt; } if (hw->converted_imgfmt != mpfmt) { MP_FATAL(p, "mid-stream hwdec format change (%s -> %s) not supported\n", mp_imgfmt_to_name(hw->converted_imgfmt), mp_imgfmt_to_name(mpfmt)); goto err; } VABufferInfo buffer_info = {.mem_type = VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME}; status = vaAcquireBufferHandle(p->display, va_image->buf, &buffer_info); if (!CHECK_VA_STATUS(p, "vaAcquireBufferHandle()")) goto err; p->buffer_acquired = true; struct mp_image layout = {0}; mp_image_set_params(&layout, &hw_image->params); mp_image_setfmt(&layout, mpfmt); // (it would be nice if we could use EGL_IMAGE_INTERNAL_FORMAT_EXT) int drm_fmts[4] = {MP_FOURCC('R', '8', ' ', ' '), // DRM_FORMAT_R8 MP_FOURCC('G', 'R', '8', '8'), // DRM_FORMAT_GR88 MP_FOURCC('R', 'G', '2', '4'), // DRM_FORMAT_RGB888 MP_FOURCC('R', 'A', '2', '4')}; // DRM_FORMAT_RGBA8888 for (int n = 0; n < layout.num_planes; n++) { int attribs[20] = {EGL_NONE}; int num_attribs = 0; ADD_ATTRIB(EGL_LINUX_DRM_FOURCC_EXT, drm_fmts[layout.fmt.bytes[n] - 1]); ADD_ATTRIB(EGL_WIDTH, mp_image_plane_w(&layout, n)); ADD_ATTRIB(EGL_HEIGHT, mp_image_plane_h(&layout, n)); ADD_ATTRIB(EGL_DMA_BUF_PLANE0_FD_EXT, buffer_info.handle); ADD_ATTRIB(EGL_DMA_BUF_PLANE0_OFFSET_EXT, va_image->offsets[n]); ADD_ATTRIB(EGL_DMA_BUF_PLANE0_PITCH_EXT, va_image->pitches[n]); p->images[n] = p->CreateImageKHR(eglGetCurrentDisplay(), EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, NULL, attribs); if (!p->images[n]) goto err; gl->BindTexture(GL_TEXTURE_2D, p->gl_textures[n]); p->EGLImageTargetTexture2DOES(GL_TEXTURE_2D, p->images[n]); out_textures[n] = p->gl_textures[n]; } gl->BindTexture(GL_TEXTURE_2D, 0); if (va_image->format.fourcc == VA_FOURCC_YV12) MPSWAP(GLuint, out_textures[1], out_textures[2]); va_unlock(p->ctx); return 0; err: va_unlock(p->ctx); MP_FATAL(p, "mapping VAAPI EGL image failed\n"); unref_image(hw); return -1; }
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, };
static void modeset_destroy_fb(int fd, struct modeset_buf *buf) { if (buf->map) { munmap(buf->map, buf->size); } if (buf->fb) { drmModeRmFB(fd, buf->fb); } if (buf->handle) { struct drm_mode_destroy_dumb dreq = { .handle = buf->handle, }; drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); } } static int modeset_create_fb(struct vo *vo, int fd, struct modeset_buf *buf) { int ret = 0; buf->handle = 0; // create dumb buffer struct drm_mode_create_dumb creq = { .width = buf->width, .height = buf->height, .bpp = 32, }; ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq); if (ret < 0) { MP_ERR(vo, "Cannot create dumb buffer: %s\n", mp_strerror(errno)); ret = -errno; goto end; } buf->stride = creq.pitch; buf->size = creq.size; buf->handle = creq.handle; // create framebuffer object for the dumb-buffer ret = drmModeAddFB(fd, buf->width, buf->height, 24, 32, buf->stride, buf->handle, &buf->fb); if (ret) { MP_ERR(vo, "Cannot create framebuffer: %s\n", mp_strerror(errno)); ret = -errno; goto end; } // prepare buffer for memory mapping struct drm_mode_map_dumb mreq = { .handle = buf->handle, }; ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq); if (ret) { MP_ERR(vo, "Cannot map dumb buffer: %s\n", mp_strerror(errno)); ret = -errno; goto end; } // perform actual memory mapping buf->map = mmap(0, buf->size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, mreq.offset); if (buf->map == MAP_FAILED) { MP_ERR(vo, "Cannot map dumb buffer: %s\n", mp_strerror(errno)); ret = -errno; goto end; } memset(buf->map, 0, buf->size); end: if (ret == 0) { return 0; } modeset_destroy_fb(fd, buf); return ret; } static int modeset_find_crtc(struct vo *vo, int fd, drmModeRes *res, drmModeConnector *conn, struct modeset_dev *dev) { for (unsigned int i = 0; i < conn->count_encoders; ++i) { drmModeEncoder *enc = drmModeGetEncoder(fd, conn->encoders[i]); if (!enc) { MP_WARN(vo, "Cannot retrieve encoder %u:%u: %s\n", i, conn->encoders[i], mp_strerror(errno)); continue; } // iterate all global CRTCs for (unsigned int j = 0; j < res->count_crtcs; ++j) { // check whether this CRTC works with the encoder if (!(enc->possible_crtcs & (1 << j))) continue; dev->enc = enc; dev->crtc = enc->crtc_id; return 0; } drmModeFreeEncoder(enc); } MP_ERR(vo, "Connector %u has no suitable CRTC\n", conn->connector_id); return -ENOENT; } static bool is_connector_valid(struct vo *vo, int conn_id, drmModeConnector *conn, bool silent) { if (!conn) { if (!silent) { MP_ERR(vo, "Cannot get connector %d: %s\n", conn_id, mp_strerror(errno)); } return false; } if (conn->connection != DRM_MODE_CONNECTED) { if (!silent) { MP_ERR(vo, "Connector %d is disconnected\n", conn_id); } return false; } if (conn->count_modes == 0) { if (!silent) { MP_ERR(vo, "Connector %d has no valid modes\n", conn_id); } return false; } return true; } static int modeset_prepare_dev(struct vo *vo, int fd, int conn_id, struct modeset_dev **out) { struct modeset_dev *dev = NULL; drmModeConnector *conn = NULL; int ret = 0; *out = NULL; drmModeRes *res = drmModeGetResources(fd); if (!res) { MP_ERR(vo, "Cannot retrieve DRM resources: %s\n", mp_strerror(errno)); ret = -errno; goto end; } if (conn_id == -1) { // get the first connected connector for (int i = 0; i < res->count_connectors; i++) { conn = drmModeGetConnector(fd, res->connectors[i]); if (is_connector_valid(vo, i, conn, true)) { conn_id = i; break; } if (conn) { drmModeFreeConnector(conn); conn = NULL; } } if (conn_id == -1) { MP_ERR(vo, "No connected connectors found\n"); ret = -ENODEV; goto end; } } if (conn_id < 0 || conn_id >= res->count_connectors) { MP_ERR(vo, "Bad connector ID. Max valid connector ID = %u\n", res->count_connectors); ret = -ENODEV; goto end; } conn = drmModeGetConnector(fd, res->connectors[conn_id]); if (!is_connector_valid(vo, conn_id, conn, false)) { ret = -ENODEV; goto end; } dev = talloc_zero(vo->priv, struct modeset_dev); dev->conn = conn->connector_id; dev->front_buf = 0; dev->mode = conn->modes[0]; dev->bufs[0].width = conn->modes[0].hdisplay; dev->bufs[0].height = conn->modes[0].vdisplay; dev->bufs[1].width = conn->modes[0].hdisplay; dev->bufs[1].height = conn->modes[0].vdisplay; MP_INFO(vo, "Connector using mode %ux%u\n", dev->bufs[0].width, dev->bufs[0].height); ret = modeset_find_crtc(vo, fd, res, conn, dev); if (ret) { MP_ERR(vo, "Connector %d has no valid CRTC\n", conn_id); goto end; } for (unsigned int i = 0; i < BUF_COUNT; i++) { ret = modeset_create_fb(vo, fd, &dev->bufs[i]); if (ret) { MP_ERR(vo, "Cannot create framebuffer for connector %d\n", conn_id); for (unsigned int j = 0; j < i; j++) { modeset_destroy_fb(fd, &dev->bufs[j]); } goto end; } } end: if (conn) { drmModeFreeConnector(conn); conn = NULL; } if (res) { drmModeFreeResources(res); res = NULL; } if (ret == 0) { *out = dev; } else { talloc_free(dev); } return ret; } static void modeset_page_flipped(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data) { struct priv *p = data; p->pflip_happening = false; } static int setup_vo_crtc(struct vo *vo) { struct priv *p = vo->priv; if (p->active) return 0; p->old_crtc = drmModeGetCrtc(p->fd, p->dev->crtc); int ret = drmModeSetCrtc(p->fd, p->dev->crtc, p->dev->bufs[p->dev->front_buf + BUF_COUNT - 1].fb, 0, 0, &p->dev->conn, 1, &p->dev->mode); p->active = true; return ret; } static void release_vo_crtc(struct vo *vo) { struct priv *p = vo->priv; if (!p->active) return; p->active = false; // wait for current page flip while (p->pflip_happening) { int ret = drmHandleEvent(p->fd, &p->ev); if (ret) { MP_ERR(vo, "drmHandleEvent failed: %i\n", ret); break; } } if (p->old_crtc) { drmModeSetCrtc(p->fd, p->old_crtc->crtc_id, p->old_crtc->buffer_id, p->old_crtc->x, p->old_crtc->y, &p->dev->conn, 1, &p->dev->mode); drmModeFreeCrtc(p->old_crtc); p->old_crtc = NULL; } } static void release_vt(void *data) { struct vo *vo = data; release_vo_crtc(vo); if (USE_MASTER) { //this function enables support for switching to x, weston etc. //however, for whatever reason, it can be called only by root users. //until things change, this is commented. struct priv *p = vo->priv; if (drmDropMaster(p->fd)) { MP_WARN(vo, "Failed to drop DRM master: %s\n", mp_strerror(errno)); } } } static void acquire_vt(void *data) { struct vo *vo = data; if (USE_MASTER) { struct priv *p = vo->priv; if (drmSetMaster(p->fd)) { MP_WARN(vo, "Failed to acquire DRM master: %s\n", mp_strerror(errno)); } } setup_vo_crtc(vo); } static int wait_events(struct vo *vo, int64_t until_time_us) { struct priv *p = vo->priv; int64_t wait_us = until_time_us - mp_time_us(); int timeout_ms = MPCLAMP((wait_us + 500) / 1000, 0, 10000); vt_switcher_poll(&p->vt_switcher, timeout_ms); return 0; } static void wakeup(struct vo *vo) { struct priv *p = vo->priv; vt_switcher_interrupt_poll(&p->vt_switcher); } static int reconfig(struct vo *vo, struct mp_image_params *params, int flags) { struct priv *p = vo->priv; vo->dwidth = p->device_w; vo->dheight = p->device_h; vo_get_src_dst_rects(vo, &p->src, &p->dst, &p->osd); int32_t w = p->dst.x1 - p->dst.x0; int32_t 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 = w; p->osd.h = 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); p->x = (p->device_w - w) >> 1; p->y = (p->device_h - h) >> 1; mp_sws_set_from_cmdline(p->sws, vo->opts->sws_opts); p->sws->src = *params; p->sws->dst = (struct mp_image_params) { .imgfmt = IMGFMT_BGR0, .w = w, .h = h, .d_w = w, .d_h = h, }; talloc_free(p->cur_frame); p->cur_frame = mp_image_alloc(IMGFMT_BGR0, p->device_w, p->device_h); mp_image_params_guess_csp(&p->sws->dst); mp_image_set_params(p->cur_frame, &p->sws->dst); struct modeset_buf *buf = p->dev->bufs; memset(buf[0].map, 0, buf[0].size); memset(buf[1].map, 0, buf[1].size); if (mp_sws_reinit(p->sws) < 0) return -1; vo->want_redraw = true; return 0; } static void draw_image(struct vo *vo, mp_image_t *mpi) { struct priv *p = vo->priv; if (p->active) { 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_sws_scale(p->sws, p->cur_frame, &src); osd_draw_on_image(vo->osd, p->osd, src.pts, 0, p->cur_frame); struct modeset_buf *front_buf = &p->dev->bufs[p->dev->front_buf]; int32_t shift = (p->device_w * p->y + p->x) * 4; memcpy_pic(front_buf->map + shift, p->cur_frame->planes[0], (p->dst.x1 - p->dst.x0) * 4, p->dst.y1 - p->dst.y0, p->device_w * 4, p->cur_frame->stride[0]); } if (mpi != p->last_input) { talloc_free(p->last_input); p->last_input = mpi; } } static void flip_page(struct vo *vo) { struct priv *p = vo->priv; if (!p->active || p->pflip_happening) return; int ret = drmModePageFlip(p->fd, p->dev->crtc, p->dev->bufs[p->dev->front_buf].fb, DRM_MODE_PAGE_FLIP_EVENT, p); if (ret) { MP_WARN(vo, "Cannot flip page for connector\n"); } else { p->dev->front_buf++; p->dev->front_buf %= BUF_COUNT; p->pflip_happening = true; } // poll page flip finish event const int timeout_ms = 3000; struct pollfd fds[1] = { { .events = POLLIN, .fd = p->fd }, }; poll(fds, 1, timeout_ms); if (fds[0].revents & POLLIN) { ret = drmHandleEvent(p->fd, &p->ev); if (ret != 0) { MP_ERR(vo, "drmHandleEvent failed: %i\n", ret); return; } } } static void uninit(struct vo *vo) { struct priv *p = vo->priv; if (p->dev) { release_vo_crtc(vo); modeset_destroy_fb(p->fd, &p->dev->bufs[1]); modeset_destroy_fb(p->fd, &p->dev->bufs[0]); drmModeFreeEncoder(p->dev->enc); } vt_switcher_destroy(&p->vt_switcher); talloc_free(p->last_input); talloc_free(p->cur_frame); talloc_free(p->dev); close(p->fd); } static int preinit(struct vo *vo) { struct priv *p = vo->priv; p->sws = mp_sws_alloc(vo); p->fd = -1; p->ev.version = DRM_EVENT_CONTEXT_VERSION; p->ev.page_flip_handler = modeset_page_flipped; if (vt_switcher_init(&p->vt_switcher, vo->log)) goto err; vt_switcher_acquire(&p->vt_switcher, acquire_vt, vo); vt_switcher_release(&p->vt_switcher, release_vt, vo); if (modeset_open(vo, &p->fd, p->device_path)) goto err; if (modeset_prepare_dev(vo, p->fd, p->connector_id, &p->dev)) goto err; assert(p->dev); p->device_w = p->dev->bufs[0].width; p->device_h = p->dev->bufs[0].height; if (setup_vo_crtc(vo)) { MP_ERR(vo, "Cannot set CRTC for connector %u: %s\n", p->connector_id, mp_strerror(errno)); goto err; } return 0; err: uninit(vo); return -1; } static int query_format(struct vo *vo, int format) { return sws_isSupportedInput(imgfmt2pixfmt(format)); } static int control(struct vo *vo, uint32_t request, void *data) { struct priv *p = vo->priv; switch (request) { case VOCTRL_SCREENSHOT_WIN: *(struct mp_image**)data = mp_image_new_copy(p->cur_frame); return VO_TRUE; case VOCTRL_REDRAW_FRAME: draw_image(vo, p->last_input); return VO_TRUE; case VOCTRL_GET_PANSCAN: return VO_TRUE; case VOCTRL_SET_PANSCAN: if (vo->config_ok) reconfig(vo, vo->params, 0); return VO_TRUE; } return VO_NOTIMPL; } #define OPT_BASE_STRUCT struct priv const struct vo_driver video_out_drm = { .name = "drm", .description = "Direct Rendering Manager", .preinit = preinit, .query_format = query_format, .reconfig = reconfig, .control = control, .draw_image = draw_image, .flip_page = flip_page, .uninit = uninit, .wait_events = wait_events, .wakeup = wakeup, .priv_size = sizeof(struct priv), .options = (const struct m_option[]) { OPT_STRING("devpath", device_path, 0), OPT_INT("connector", connector_id, 0), {0}, }, .priv_defaults = &(const struct priv) {