diff --git a/Makefile b/Makefile index 92ef2c7cf5..4491571255 100644 --- a/Makefile +++ b/Makefile @@ -106,7 +106,9 @@ SOURCES-$(OSS) += audio/out/ao_oss.c SOURCES-$(PULSE) += audio/out/ao_pulse.c SOURCES-$(PORTAUDIO) += audio/out/ao_portaudio.c SOURCES-$(RSOUND) += audio/out/ao_rsound.c -SOURCES-$(VDPAU) += video/out/vo_vdpau.c +SOURCES-$(VDPAU) += video/vdpau.c video/out/vo_vdpau.c +SOURCES-$(VDPAU_DEC) += video/decode/vdpau.c +SOURCES-$(VDPAU_DEC_OLD) += video/decode/vdpau_old.c SOURCES-$(X11) += video/out/vo_x11.c video/out/x11_common.c SOURCES-$(XV) += video/out/vo_xv.c diff --git a/configure b/configure index c108064ee8..4bf7e271f8 100755 --- a/configure +++ b/configure @@ -2635,6 +2635,23 @@ else def_avresample_has_set_channel_mapping='#define HAVE_AVRESAMPLE_SET_CHANNEL_MAPPING 0' fi +_vdpau_dec=no +_vdpau_dec_old=no +if test "$_vdpau" = yes ; then + +echocheck "libavcodec new vdpau API" +_avcodec_new_vdpau_api=no +statement_check libavutil/pixfmt.h 'int x = AV_PIX_FMT_VDPAU' && _avcodec_new_vdpau_api=yes +if test "$_avcodec_new_vdpau_api" = yes ; then + def_avcodec_new_vdpau_api='#define HAVE_AV_CODEC_NEW_VDPAU_API 1' + _vdpau_dec=yes +else + def_avcodec_new_vdpau_api='#define HAVE_AV_CODEC_NEW_VDPAU_API 0' + _vdpau_dec_old=yes +fi +echores "$_avcodec_new_vdpau_api" + +fi echocheck "libavcodec AV_CODEC_PROP_TEXT_SUB API" _avcodec_has_text_flag_api=no @@ -3077,6 +3094,8 @@ TV = $_tv TV_V4L2 = $_tv_v4l2 VCD = $_vcd VDPAU = $_vdpau +VDPAU_DEC = $_vdpau_dec +VDPAU_DEC_OLD = $_vdpau_dec_old WIN32 = $_win32 X11 = $_x11 WAYLAND = $_wayland @@ -3179,6 +3198,7 @@ $def_zlib $def_avutil_has_refcounting $def_avutil_has_qp_api +$def_avcodec_new_vdpau_api $def_avcodec_has_text_flag_api $def_avcodec_has_chroma_pos_api $def_libpostproc diff --git a/core/mplayer.c b/core/mplayer.c index 44cc3f5540..4ccfc6f526 100644 --- a/core/mplayer.c +++ b/core/mplayer.c @@ -2319,7 +2319,7 @@ int reinit_video_chain(struct MPContext *mpctx) struct MPOpts *opts = mpctx->opts; assert(!(mpctx->initialized_flags & INITIALIZED_VCODEC)); init_demux_stream(mpctx, STREAM_VIDEO); - sh_video_t * const sh_video = mpctx->sh_video; + sh_video_t *sh_video = mpctx->sh_video; if (!sh_video) { uninit_player(mpctx, INITIALIZED_VO); goto no_video; @@ -2354,6 +2354,11 @@ int reinit_video_chain(struct MPContext *mpctx) &(bool){false}); } mpctx->initialized_flags |= INITIALIZED_VO; + + // dynamic allocation only to make stheader.h lighter + talloc_free(sh_video->hwdec_info); + sh_video->hwdec_info = talloc_zero(sh_video, struct mp_hwdec_info); + vo_control(mpctx->video_out, VOCTRL_GET_HWDEC_INFO, sh_video->hwdec_info); } vo_update_window_title(mpctx); diff --git a/demux/stheader.h b/demux/stheader.h index e90909e17a..6109221442 100644 --- a/demux/stheader.h +++ b/demux/stheader.h @@ -127,11 +127,12 @@ typedef struct sh_video { float stream_aspect; // aspect ratio in media headers (DVD IFO files) int i_bps; // == bitrate (compressed bytes/sec) int disp_w, disp_h; // display size (filled by demuxer or decoder) - struct mp_image_params *vf_input; // video filter input params // output driver/filters: (set by libmpcodecs core) struct vf_instance *vfilter; // video filter chain const struct vd_functions *vd_driver; int vf_initialized; // -1 failed, 0 not done, 1 done + struct mp_image_params *vf_input; // video filter input params + struct mp_hwdec_info *hwdec_info; // video output hwdec handles // win32-compatible codec parameters: BITMAPINFOHEADER *bih; } sh_video_t; diff --git a/video/decode/dec_video.h b/video/decode/dec_video.h index 141442fa19..4ba052afd1 100644 --- a/video/decode/dec_video.h +++ b/video/decode/dec_video.h @@ -47,4 +47,10 @@ int vd_control(struct sh_video *sh_video, int cmd, void *arg); extern int divx_quality; +// Used to communicate hardware decoder API handles from VO to video decoder. +// The VO can set the context pointer for supported APIs. +struct mp_hwdec_info { + struct mp_vdpau_ctx *vdpau_ctx; +}; + #endif /* MPLAYER_DEC_VIDEO_H */ diff --git a/video/decode/lavc.h b/video/decode/lavc.h index 25ed2b8ac5..3611530400 100644 --- a/video/decode/lavc.h +++ b/video/decode/lavc.h @@ -10,7 +10,7 @@ #include "demux/stheader.h" #include "video/mp_image.h" -typedef struct ffmpeg_ctx { +typedef struct lavc_ctx { AVCodecContext *avctx; AVFrame *pic; struct hwdec *hwdec; @@ -23,11 +23,28 @@ typedef struct ffmpeg_ctx { enum AVDiscard skip_frame; const char *software_fallback_decoder; + // From VO + struct mp_hwdec_info *hwdec_info; + + // For free use by hwdec implementation + void *hwdec_priv; + + // Legacy bool do_dr1; struct FramePool *dr1_buffer_pool; struct mp_image_pool *non_dr1_pool; } vd_ffmpeg_ctx; +struct vd_lavc_hwdec_functions { + // If not-NULL, a 0 terminated list of IMGFMT_ formats. Only one of these + // formats is accepted when handling the libavcodec get_format callback. + const int *image_formats; + int (*init)(struct lavc_ctx *ctx); + void (*uninit)(struct lavc_ctx *ctx); + struct mp_image *(*allocate_image)(struct lavc_ctx *ctx, AVFrame *frame); + void (*fix_image)(struct lavc_ctx *ctx, struct mp_image *img); +}; + // lavc_dr1.c int mp_codec_get_buffer(AVCodecContext *s, AVFrame *frame); void mp_codec_release_buffer(AVCodecContext *s, AVFrame *frame); diff --git a/video/decode/lavc_dr1.c b/video/decode/lavc_dr1.c index 5dc73c3ea8..15fc44a445 100644 --- a/video/decode/lavc_dr1.c +++ b/video/decode/lavc_dr1.c @@ -137,7 +137,7 @@ static int alloc_buffer(FramePool *pool, AVCodecContext *s) int mp_codec_get_buffer(AVCodecContext *s, AVFrame *frame) { sh_video_t *sh = s->opaque; - struct ffmpeg_ctx *ctx = sh->context; + struct lavc_ctx *ctx = sh->context; if (!ctx->dr1_buffer_pool) { ctx->dr1_buffer_pool = av_mallocz(sizeof(*ctx->dr1_buffer_pool)); diff --git a/video/decode/vd_lavc.c b/video/decode/vd_lavc.c index 3ee1b40bb8..ea12126ffb 100644 --- a/video/decode/vd_lavc.c +++ b/video/decode/vd_lavc.c @@ -34,6 +34,7 @@ #include "config.h" #include "core/mp_msg.h" #include "core/options.h" +#include "core/bstr.h" #include "core/av_opts.h" #include "core/av_common.h" #include "core/codecs.h" @@ -50,7 +51,6 @@ #include "osdep/numcores.h" #include "video/csputils.h" -#include "libavcodec/avcodec.h" #include "lavc.h" #if AVPALETTE_SIZE != MP_PALETTE_SIZE @@ -62,8 +62,6 @@ static void init_avctx(sh_video_t *sh, const char *decoder, struct hwdec *hwdec); static void uninit_avctx(sh_video_t *sh); static void setup_refcounting_hw(struct AVCodecContext *s); -static void draw_slice_hwdec(struct AVCodecContext *s, const AVFrame *src, - int offset[4], int y, int type, int height); static enum PixelFormat get_format_hwdec(struct AVCodecContext *avctx, const enum PixelFormat *pix_fmt); @@ -94,17 +92,32 @@ enum hwdec_type { struct hwdec { enum hwdec_type api; const char *codec, *hw_codec; + const struct vd_lavc_hwdec_functions *fns; }; +const struct vd_lavc_hwdec_functions mp_vd_lavc_vdpau; +const struct vd_lavc_hwdec_functions mp_vd_lavc_vdpau_old; + static const struct hwdec hwdec_list[] = { - {HWDEC_VDPAU, "h264", "h264_vdpau"}, - {HWDEC_VDPAU, "wmv3", "wmv3_vdpau"}, - {HWDEC_VDPAU, "vc1", "vc1_vdpau"}, - {HWDEC_VDPAU, "mpegvideo", "mpegvideo_vdpau"}, - {HWDEC_VDPAU, "mpeg1video", "mpeg1video_vdpau"}, - {HWDEC_VDPAU, "mpeg2video", "mpegvideo_vdpau"}, - {HWDEC_VDPAU, "mpeg2", "mpeg2_vdpau"}, - {HWDEC_VDPAU, "mpeg4", "mpeg4_vdpau"}, +#ifdef CONFIG_VDPAU +#if HAVE_AV_CODEC_NEW_VDPAU_API + {HWDEC_VDPAU, "h264", NULL, &mp_vd_lavc_vdpau}, + {HWDEC_VDPAU, "wmv3", NULL, &mp_vd_lavc_vdpau}, + {HWDEC_VDPAU, "vc1", NULL, &mp_vd_lavc_vdpau}, + {HWDEC_VDPAU, "mpeg1video", NULL, &mp_vd_lavc_vdpau}, + {HWDEC_VDPAU, "mpeg2video", NULL, &mp_vd_lavc_vdpau}, + {HWDEC_VDPAU, "mpeg4", NULL, &mp_vd_lavc_vdpau}, +#else + {HWDEC_VDPAU, "h264", "h264_vdpau", &mp_vd_lavc_vdpau_old}, + {HWDEC_VDPAU, "wmv3", "wmv3_vdpau", &mp_vd_lavc_vdpau_old}, + {HWDEC_VDPAU, "vc1", "vc1_vdpau", &mp_vd_lavc_vdpau_old}, + {HWDEC_VDPAU, "mpegvideo", "mpegvideo_vdpau", &mp_vd_lavc_vdpau_old}, + {HWDEC_VDPAU, "mpeg1video", "mpeg1video_vdpau", &mp_vd_lavc_vdpau_old}, + {HWDEC_VDPAU, "mpeg2video", "mpegvideo_vdpau", &mp_vd_lavc_vdpau_old}, + {HWDEC_VDPAU, "mpeg2", "mpeg2_vdpau", &mp_vd_lavc_vdpau_old}, + {HWDEC_VDPAU, "mpeg4", "mpeg4_vdpau", &mp_vd_lavc_vdpau_old}, +#endif +#endif // CONFIG_VDPAU {HWDEC_VDA, "h264", "h264_vda"}, @@ -158,17 +171,33 @@ static int init(sh_video_t *sh, const char *decoder) ctx = sh->context = talloc_zero(NULL, vd_ffmpeg_ctx); ctx->non_dr1_pool = talloc_steal(ctx, mp_image_pool_new(16)); + if (bstr_endswith0(bstr0(decoder), "_vdpau")) { + mp_tmsg(MSGT_DECVIDEO, MSGL_WARN, "VDPAU decoder '%s' was requested. " + "This way of enabling hardware\ndecoding is not supported " + "anymore. Use --hwdec=vdpau instead.\nThe --hwdec-codec=... " + "option can be used to restrict which codecs are\nenabled, " + "otherwise all hardware decoding is tried for all codecs.\n", + decoder); + uninit(sh); + return 0; + } + struct hwdec *hwdec = find_hwcodec(sh->opts->hwdec_api, decoder); struct hwdec *use_hwdec = NULL; if (hwdec && hwdec_codec_allowed(sh, hwdec)) { - AVCodec *lavc_hwcodec = avcodec_find_decoder_by_name(hwdec->hw_codec); - if (lavc_hwcodec) { - ctx->software_fallback_decoder = talloc_strdup(ctx, decoder); - decoder = lavc_hwcodec->name; - use_hwdec = hwdec; + if (hwdec->hw_codec) { + AVCodec *lavc_hwcodec = avcodec_find_decoder_by_name(hwdec->hw_codec); + if (lavc_hwcodec) { + ctx->software_fallback_decoder = talloc_strdup(ctx, decoder); + decoder = lavc_hwcodec->name; + use_hwdec = hwdec; + } else { + mp_tmsg(MSGT_DECVIDEO, MSGL_WARN, "Decoder '%s' not found in " + "libavcodec, using software decoding.\n", hwdec->hw_codec); + } } else { - mp_tmsg(MSGT_DECVIDEO, MSGL_WARN, "Decoder '%s' not found in " - "libavcodec, using software decoding.\n", hwdec->hw_codec); + ctx->software_fallback_decoder = talloc_strdup(ctx, decoder); + use_hwdec = hwdec; } } @@ -264,6 +293,8 @@ static void init_avctx(sh_video_t *sh, const char *decoder, struct hwdec *hwdec) if (!lavc_codec) return; + ctx->hwdec_info = sh->hwdec_info; + ctx->do_dr1 = ctx->do_hw_dr1 = 0; ctx->pix_fmt = PIX_FMT_NONE; ctx->vo_initialized = 0; @@ -277,26 +308,15 @@ static void init_avctx(sh_video_t *sh, const char *decoder, struct hwdec *hwdec) avctx->thread_count = lavc_param->threads; - // Hack to allow explicitly selecting vdpau hw decoders - if (!hwdec && (lavc_codec->capabilities & CODEC_CAP_HWACCEL_VDPAU)) { - ctx->hwdec = talloc(ctx, struct hwdec); - *ctx->hwdec = (struct hwdec) { - .api = HWDEC_VDPAU, - .codec = sh->gsh->codec, - .hw_codec = decoder, - }; - } - - if (ctx->hwdec && ctx->hwdec->api == HWDEC_VDPAU) { - assert(lavc_codec->capabilities & CODEC_CAP_HWACCEL_VDPAU); + if (ctx->hwdec && ctx->hwdec->fns) { ctx->do_hw_dr1 = true; avctx->thread_count = 1; - avctx->get_format = get_format_hwdec; + if (ctx->hwdec->fns->image_formats) + avctx->get_format = get_format_hwdec; setup_refcounting_hw(avctx); - if (ctx->hwdec->api == HWDEC_VDPAU) { - avctx->draw_horiz_band = draw_slice_hwdec; - avctx->slice_flags = - SLICE_FLAG_CODED_ORDER | SLICE_FLAG_ALLOW_FIELD; + if (ctx->hwdec->fns->init(ctx) < 0) { + uninit_avctx(sh); + return; } } else { #if HAVE_AVUTIL_REFCOUNTING @@ -381,6 +401,9 @@ static void uninit_avctx(sh_video_t *sh) av_freep(&ctx->avctx); avcodec_free_frame(&ctx->pic); + if (ctx->hwdec && ctx->hwdec->fns) + ctx->hwdec->fns->uninit(ctx); + #if !HAVE_AVUTIL_REFCOUNTING mp_buffer_pool_free(&ctx->dr1_buffer_pool); #endif @@ -406,11 +429,6 @@ static int init_vo(sh_video_t *sh, AVFrame *frame) pix_fmt = ctx->avctx->pix_fmt; #endif - /* Reconfiguring filter/VO chain may invalidate direct rendering buffers - * we have allocated for libavcodec (including the VDPAU HW decoding - * case). Is it guaranteed that the code below only triggers in a situation - * with no busy direct rendering buffers for reference frames? - */ if (av_cmp_q(frame->sample_aspect_ratio, ctx->last_sample_aspect_ratio) || width != sh->disp_w || height != sh->disp_h || pix_fmt != ctx->pix_fmt || !ctx->vo_initialized) @@ -466,27 +484,19 @@ static enum PixelFormat get_format_hwdec(struct AVCodecContext *avctx, mp_msg(MSGT_DECVIDEO, MSGL_V, " %s", av_get_pix_fmt_name(fmt[i])); mp_msg(MSGT_DECVIDEO, MSGL_V, "\n"); - assert(ctx->hwdec); + assert(ctx->hwdec && ctx->hwdec->fns); for (int i = 0; fmt[i] != PIX_FMT_NONE; i++) { - int imgfmt = pixfmt2imgfmt(fmt[i]); - if (ctx->hwdec->api == HWDEC_VDPAU && IMGFMT_IS_VDPAU(imgfmt)) - return fmt[i]; + const int *okfmt = ctx->hwdec->fns->image_formats; + for (int n = 0; okfmt && okfmt[n]; n++) { + if (imgfmt2pixfmt(okfmt[n]) == fmt[i]) + return fmt[i]; + } } return PIX_FMT_NONE; } -static void draw_slice_hwdec(struct AVCodecContext *s, - const AVFrame *src, int offset[4], - int y, int type, int height) -{ - sh_video_t *sh = s->opaque; - struct vf_instance *vf = sh->vfilter; - void *state_ptr = src->data[0]; - vf->control(vf, VFCTRL_HWDEC_DECODER_RENDER, state_ptr); -} - static struct mp_image *get_surface_hwdec(struct sh_video *sh, AVFrame *pic) { vd_ffmpeg_ctx *ctx = sh->context; @@ -506,24 +516,7 @@ static struct mp_image *get_surface_hwdec(struct sh_video *sh, AVFrame *pic) if (!IMGFMT_IS_HWACCEL(imgfmt)) return NULL; - // Video with non mod-16 width/height will have allocation sizes that are - // rounded up. This conflicts with our video size change detection and - // leads to an endless loop. On the other hand, vdpau seems to round up - // frame allocations internally. So use the original video resolution - // instead. - AVFrame pic_resized = *pic; - pic_resized.width = ctx->avctx->width; - pic_resized.height = ctx->avctx->height; - - if (init_vo(sh, &pic_resized) < 0) - return NULL; - - assert(IMGFMT_IS_HWACCEL(ctx->best_csp)); - - struct mp_image *mpi = NULL; - - struct vf_instance *vf = sh->vfilter; - vf->control(vf, VFCTRL_HWDEC_ALLOC_SURFACE, &mpi); + struct mp_image *mpi = ctx->hwdec->fns->allocate_image(ctx, pic); if (mpi) { for (int i = 0; i < 4; i++) @@ -698,6 +691,9 @@ static int decode(struct sh_video *sh, struct demux_packet *packet, struct mp_image *mpi = image_from_decoder(sh); assert(mpi->planes[0]); + if (ctx->hwdec && ctx->hwdec->fns && ctx->hwdec->fns->fix_image) + ctx->hwdec->fns->fix_image(ctx, mpi); + mpi->colorspace = ctx->image_params.colorspace; mpi->levels = ctx->image_params.colorlevels; mpi->chroma_location = ctx->image_params.chroma_location; diff --git a/video/decode/vdpau.c b/video/decode/vdpau.c new file mode 100644 index 0000000000..512f9170a3 --- /dev/null +++ b/video/decode/vdpau.c @@ -0,0 +1,228 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with mpv. If not, see . + */ + +#include +#include + +#include +#include + +#include "lavc.h" +#include "core/mp_common.h" +#include "video/fmt-conversion.h" +#include "video/vdpau.h" +#include "video/decode/dec_video.h" + +struct priv { + struct mp_vdpau_ctx *mpvdp; + struct vdp_functions *vdp; + VdpDevice vdp_device; + uint64_t preemption_counter; + + AVVDPAUContext context; + + int vid_width; + int vid_height; +}; + +struct profile_entry { + enum AVCodecID av_codec; + int ff_profile; + VdpDecoderProfile vdp_profile; + int maxrefs; +}; + +#define PE(av_codec_id, ff_profile, vdp_dcoder_profile, maxrefs) \ + {AV_CODEC_ID_ ## av_codec_id, \ + FF_PROFILE_ ## ff_profile, \ + VDP_DECODER_PROFILE_ ## vdp_dcoder_profile, \ + maxrefs} + +static const struct profile_entry profiles[] = { + PE(MPEG1VIDEO, UNKNOWN, MPEG1, 2), + PE(MPEG2VIDEO, MPEG2_SIMPLE, MPEG2_SIMPLE, 2), + PE(MPEG2VIDEO, UNKNOWN, MPEG2_MAIN, 2), + PE(H264, H264_BASELINE, H264_BASELINE, 16), + PE(H264, H264_CONSTRAINED_BASELINE, H264_BASELINE, 16), + PE(H264, H264_MAIN, H264_MAIN, 16), + PE(H264, UNKNOWN, H264_HIGH, 16), + PE(WMV3, VC1_SIMPLE, VC1_SIMPLE, 2), + PE(WMV3, VC1_MAIN, VC1_MAIN, 2), + PE(WMV3, UNKNOWN, VC1_ADVANCED, 2), + PE(VC1, VC1_SIMPLE, VC1_SIMPLE, 2), + PE(VC1, VC1_MAIN, VC1_MAIN, 2), + PE(VC1, UNKNOWN, VC1_ADVANCED, 2), + PE(MPEG4, MPEG4_SIMPLE, MPEG4_PART2_SP, 2), + PE(MPEG4, UNKNOWN, MPEG4_PART2_ASP,2), +}; + +// libavcodec absolutely wants a non-NULL render callback +static VdpStatus dummy_render( + VdpDecoder decoder, + VdpVideoSurface target, + VdpPictureInfo const * picture_info, + uint32_t bitstream_buffer_count, + VdpBitstreamBuffer const * bitstream_buffers) +{ + return VDP_STATUS_DISPLAY_PREEMPTED; +} + +static void mark_uninitialized(struct lavc_ctx *ctx) +{ + struct priv *p = ctx->hwdec_priv; + + p->vdp_device = VDP_INVALID_HANDLE; + p->context.decoder = VDP_INVALID_HANDLE; + p->context.render = dummy_render; +} + +static int handle_preemption(struct lavc_ctx *ctx) +{ + struct priv *p = ctx->hwdec_priv; + + if (!mp_vdpau_status_ok(p->mpvdp)) + return -1; + + // Mark objects as destroyed if preemption+reinit occured + if (p->preemption_counter < p->mpvdp->preemption_counter) { + p->preemption_counter = p->mpvdp->preemption_counter; + mark_uninitialized(ctx); + } + + p->vdp_device = p->mpvdp->vdp_device; + p->vdp = p->mpvdp->vdp; + + return 0; +} + +static bool create_vdp_decoder(struct lavc_ctx *ctx) +{ + struct priv *p = ctx->hwdec_priv; + struct vdp_functions *vdp = p->mpvdp->vdp; + VdpStatus vdp_st; + + if (handle_preemption(ctx) < 0) + return false; + + if (p->context.decoder != VDP_INVALID_HANDLE) + vdp->decoder_destroy(p->context.decoder); + + const struct profile_entry *pe = NULL; + for (int n = 0; n < MP_ARRAY_SIZE(profiles); n++) { + if (profiles[n].av_codec == ctx->avctx->codec_id && + (profiles[n].ff_profile == ctx->avctx->profile || + profiles[n].ff_profile == FF_PROFILE_UNKNOWN)) + { + pe = &profiles[n]; + break; + } + } + + if (!pe) { + mp_msg(MSGT_VO, MSGL_ERR, "[vdpau] Unknown codec!\n"); + goto fail; + } + + vdp_st = vdp->decoder_create(p->vdp_device, pe->vdp_profile, + p->vid_width, p->vid_height, pe->maxrefs, + &p->context.decoder); + CHECK_ST_WARNING("Failed creating VDPAU decoder"); + p->context.render = p->vdp->decoder_render; + if (vdp_st != VDP_STATUS_OK) + goto fail; + return true; + +fail: + p->context.decoder = VDP_INVALID_HANDLE; + p->context.render = dummy_render; + return false; +} + +static struct mp_image *allocate_image(struct lavc_ctx *ctx, AVFrame *frame) +{ + struct priv *p = ctx->hwdec_priv; + + if (frame->format != AV_PIX_FMT_VDPAU) + return NULL; + + // frame->width/height lie. Using them breaks with non-mod 16 video. + int w = ctx->avctx->width; + int h = ctx->avctx->height; + + handle_preemption(ctx); + + if (w != p->vid_width || h != p->vid_height || + p->context.decoder == VDP_INVALID_HANDLE) + { + p->vid_width = w; + p->vid_height = h; + if (!create_vdp_decoder(ctx)) + return NULL; + } + + VdpChromaType chroma; + mp_vdpau_get_format(IMGFMT_VDPAU, &chroma, NULL); + + return mp_vdpau_get_video_surface(p->mpvdp, IMGFMT_VDPAU, chroma, w, h); +} + +static void uninit(struct lavc_ctx *ctx) +{ + struct priv *p = ctx->hwdec_priv; + + if (!p) + return; + + if (p->context.decoder != VDP_INVALID_HANDLE) + p->vdp->decoder_destroy(p->context.decoder); + + // Free bitstream buffers allocated by libavcodec + av_freep(&p->context.bitstream_buffers); + + talloc_free(p); + + ctx->hwdec_priv = NULL; +} + +static int init(struct lavc_ctx *ctx) +{ + if (!ctx->hwdec_info || !ctx->hwdec_info->vdpau_ctx) + return -1; + + struct priv *p = talloc_ptrtype(NULL, p); + *p = (struct priv) { + .mpvdp = ctx->hwdec_info->vdpau_ctx, + }; + ctx->hwdec_priv = p; + + p->preemption_counter = p->mpvdp->preemption_counter; + mark_uninitialized(ctx); + + if (handle_preemption(ctx) < 0) + return -1; + + ctx->avctx->hwaccel_context = &p->context; + + return 0; +} + +const struct vd_lavc_hwdec_functions mp_vd_lavc_vdpau = { + .image_formats = (const int[]) {IMGFMT_VDPAU, 0}, + .init = init, + .uninit = uninit, + .allocate_image = allocate_image, +}; diff --git a/video/decode/vdpau_old.c b/video/decode/vdpau_old.c new file mode 100644 index 0000000000..e9c88b69ea --- /dev/null +++ b/video/decode/vdpau_old.c @@ -0,0 +1,267 @@ +/* + * VDPAU video output driver + * + * Copyright (C) 2008 NVIDIA + * Copyright (C) 2009 Uoti Urpala + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include + +#include +#include + +#include "lavc.h" +#include "video/fmt-conversion.h" +#include "video/vdpau.h" +#include "video/decode/dec_video.h" + +struct priv { + struct mp_vdpau_ctx *mpvdp; + struct vdp_functions *vdp; + VdpDevice vdp_device; + uint64_t preemption_counter; + + int image_format; + int vid_width; + int vid_height; + + VdpDecoder decoder; + int decoder_max_refs; +}; + +static void mark_uninitialized(struct lavc_ctx *ctx) +{ + struct priv *p = ctx->hwdec_priv; + + p->vdp_device = VDP_INVALID_HANDLE; + p->decoder = VDP_INVALID_HANDLE; +} + +static int handle_preemption(struct lavc_ctx *ctx) +{ + struct priv *p = ctx->hwdec_priv; + + if (!mp_vdpau_status_ok(p->mpvdp)) + return -1; + + // Mark objects as destroyed if preemption+reinit occured + if (p->preemption_counter < p->mpvdp->preemption_counter) { + p->preemption_counter = p->mpvdp->preemption_counter; + mark_uninitialized(ctx); + } + + p->vdp_device = p->mpvdp->vdp_device; + p->vdp = p->mpvdp->vdp; + + return 0; +} + +static bool create_vdp_decoder(struct lavc_ctx *ctx, int max_refs) +{ + struct priv *p = ctx->hwdec_priv; + struct vdp_functions *vdp = p->mpvdp->vdp; + VdpStatus vdp_st; + VdpDecoderProfile vdp_decoder_profile; + + if (handle_preemption(ctx) < 0) + return false; + + if (p->decoder != VDP_INVALID_HANDLE) + vdp->decoder_destroy(p->decoder); + + switch (p->image_format) { + case IMGFMT_VDPAU_MPEG1: + vdp_decoder_profile = VDP_DECODER_PROFILE_MPEG1; + break; + case IMGFMT_VDPAU_MPEG2: + vdp_decoder_profile = VDP_DECODER_PROFILE_MPEG2_MAIN; + break; + case IMGFMT_VDPAU_H264: + vdp_decoder_profile = VDP_DECODER_PROFILE_H264_HIGH; + mp_msg(MSGT_VO, MSGL_V, "[vdpau] Creating H264 hardware decoder " + "for %d reference frames.\n", max_refs); + break; + case IMGFMT_VDPAU_WMV3: + vdp_decoder_profile = VDP_DECODER_PROFILE_VC1_MAIN; + break; + case IMGFMT_VDPAU_VC1: + vdp_decoder_profile = VDP_DECODER_PROFILE_VC1_ADVANCED; + break; + case IMGFMT_VDPAU_MPEG4: + vdp_decoder_profile = VDP_DECODER_PROFILE_MPEG4_PART2_ASP; + break; + default: + mp_msg(MSGT_VO, MSGL_ERR, "[vdpau] Unknown image format!\n"); + goto fail; + } + vdp_st = vdp->decoder_create(p->vdp_device, vdp_decoder_profile, + p->vid_width, p->vid_height, max_refs, + &p->decoder); + CHECK_ST_WARNING("Failed creating VDPAU decoder"); + if (vdp_st != VDP_STATUS_OK) + goto fail; + p->decoder_max_refs = max_refs; + return true; + +fail: + p->decoder = VDP_INVALID_HANDLE; + p->decoder_max_refs = 0; + return false; +} + +static void draw_slice_hwdec(struct AVCodecContext *s, + const AVFrame *src, int offset[4], + int y, int type, int height) +{ + sh_video_t *sh = s->opaque; + struct lavc_ctx *ctx = sh->context; + struct priv *p = ctx->hwdec_priv; + struct vdp_functions *vdp = p->vdp; + VdpStatus vdp_st; + + if (handle_preemption(ctx) < 0) + return; + + struct vdpau_render_state *rndr = (void *)src->data[0]; + + int max_refs = p->image_format == IMGFMT_VDPAU_H264 ? + rndr->info.h264.num_ref_frames : 2; + if ((p->decoder == VDP_INVALID_HANDLE || p->decoder_max_refs < max_refs) + && !create_vdp_decoder(ctx, max_refs)) + return; + + vdp_st = vdp->decoder_render(p->decoder, rndr->surface, + (void *)&rndr->info, + rndr->bitstream_buffers_used, + rndr->bitstream_buffers); + CHECK_ST_WARNING("Failed VDPAU decoder rendering"); +} + +static void release_surface(void *ptr) +{ + struct vdpau_render_state *state = ptr; + // Free bitstream buffers allocated by libavcodec + av_freep(&state->bitstream_buffers); + talloc_free(state); +} + +static struct mp_image *allocate_image(struct lavc_ctx *ctx, AVFrame *frame) +{ + struct priv *p = ctx->hwdec_priv; + int imgfmt = pixfmt2imgfmt(frame->format); + + if (!IMGFMT_IS_VDPAU(imgfmt)) + return NULL; + + // frame->width/height lie. Using them breaks with non-mod 16 video. + int w = ctx->avctx->width; + int h = ctx->avctx->height; + + if (w != p->vid_width || h != p->vid_height || imgfmt != p->image_format) { + p->vid_width = w; + p->vid_height = h; + p->image_format = imgfmt; + if (!create_vdp_decoder(ctx, 2)) + return NULL; + } + + VdpChromaType chroma; + mp_vdpau_get_format(p->image_format, &chroma, NULL); + + struct mp_image *img = + mp_vdpau_get_video_surface(p->mpvdp, imgfmt, chroma, w, h); + + if (!img) + return NULL; + + // Create chained reference for vdpau_render_state. This will track the + // lifetime of the actual reference too. + // This is quite roundabout, but at least it allows us to share the + // surface allocator in vo_vdpau.c with the new vdpau code. + + struct vdpau_render_state *state = talloc_ptrtype(NULL, state); + memset(state, 0, sizeof(*state)); + state->surface = (VdpVideoSurface)(intptr_t)img->planes[3]; + + talloc_steal(state, img); + + struct mp_image *new = mp_image_new_custom_ref(img, state, release_surface); + new->planes[0] = (void *)state; + return new; +} + +static void uninit(struct lavc_ctx *ctx) +{ + struct priv *p = ctx->hwdec_priv; + + if (!p) + return; + + if (p->decoder != VDP_INVALID_HANDLE) + p->vdp->decoder_destroy(p->decoder); + + talloc_free(p); + ctx->hwdec_priv = NULL; +} + +static int init(struct lavc_ctx *ctx) +{ + if (!ctx->hwdec_info || !ctx->hwdec_info->vdpau_ctx) + return -1; + + struct priv *p = talloc_ptrtype(NULL, p); + *p = (struct priv) { + .mpvdp = ctx->hwdec_info->vdpau_ctx, + }; + ctx->hwdec_priv = p; + + p->preemption_counter = p->mpvdp->preemption_counter; + mark_uninitialized(ctx); + + if (handle_preemption(ctx) < 0) + return -1; + + AVCodecContext *avctx = ctx->avctx; + + avctx->draw_horiz_band = draw_slice_hwdec; + avctx->slice_flags = SLICE_FLAG_CODED_ORDER | SLICE_FLAG_ALLOW_FIELD; + + return 0; +} + +static void fix_image(struct lavc_ctx *ctx, struct mp_image *img) +{ + // Make it follow the convention of the "new" vdpau decoder + struct vdpau_render_state *rndr = (void *)img->planes[0]; + img->planes[0] = (void *)"dummy"; // must be non-NULL, otherwise arbitrary + img->planes[3] = (void *)(intptr_t)rndr->surface; +} + +const struct vd_lavc_hwdec_functions mp_vd_lavc_vdpau_old = { + .image_formats = (const int[]) { + IMGFMT_VDPAU_MPEG1, IMGFMT_VDPAU_MPEG2, IMGFMT_VDPAU_H264, + IMGFMT_VDPAU_WMV3, IMGFMT_VDPAU_VC1, IMGFMT_VDPAU_MPEG4, + 0 + }, + .init = init, + .uninit = uninit, + .allocate_image = allocate_image, + .fix_image = fix_image, +}; diff --git a/video/filter/vf.h b/video/filter/vf.h index d115890f64..c792049b3b 100644 --- a/video/filter/vf.h +++ b/video/filter/vf.h @@ -99,8 +99,6 @@ typedef struct vf_seteq { #define VFCTRL_SET_PP_LEVEL 5 // set postprocessing level #define VFCTRL_SET_EQUALIZER 6 // set color options (brightness,contrast etc) #define VFCTRL_GET_EQUALIZER 8 // get color options (brightness,contrast etc) -#define VFCTRL_HWDEC_DECODER_RENDER 9 // vdpau hw decoding -#define VFCTRL_HWDEC_ALLOC_SURFACE 10 // vdpau hw decoding #define VFCTRL_SCREENSHOT 14 // Take screenshot, arg is voctrl_screenshot_args #define VFCTRL_INIT_OSD 15 // Filter OSD renderer present? #define VFCTRL_SET_DEINTERLACE 18 // Set deinterlacing status diff --git a/video/filter/vf_vo.c b/video/filter/vf_vo.c index 60113192b7..9edb393e23 100644 --- a/video/filter/vf_vo.c +++ b/video/filter/vf_vo.c @@ -87,10 +87,6 @@ static int control(struct vf_instance *vf, int request, void *data) }; return vo_control(video_out, VOCTRL_GET_EQUALIZER, ¶m) == VO_TRUE; } - case VFCTRL_HWDEC_DECODER_RENDER: - return vo_control(video_out, VOCTRL_HWDEC_DECODER_RENDER, data); - case VFCTRL_HWDEC_ALLOC_SURFACE: - return vo_control(video_out, VOCTRL_HWDEC_ALLOC_SURFACE, data); } return CONTROL_UNKNOWN; } diff --git a/video/fmt-conversion.c b/video/fmt-conversion.c index 153b1badf3..500b4b1609 100644 --- a/video/fmt-conversion.c +++ b/video/fmt-conversion.c @@ -171,12 +171,18 @@ static const struct { {IMGFMT_BGRA64_LE, PIX_FMT_BGRA64LE}, #endif +#if HAVE_AV_CODEC_NEW_VDPAU_API + {IMGFMT_VDPAU, AV_PIX_FMT_VDPAU}, +#else {IMGFMT_VDPAU_MPEG1, PIX_FMT_VDPAU_MPEG1}, {IMGFMT_VDPAU_MPEG2, PIX_FMT_VDPAU_MPEG2}, {IMGFMT_VDPAU_H264, PIX_FMT_VDPAU_H264}, {IMGFMT_VDPAU_WMV3, PIX_FMT_VDPAU_WMV3}, {IMGFMT_VDPAU_VC1, PIX_FMT_VDPAU_VC1}, {IMGFMT_VDPAU_MPEG4, PIX_FMT_VDPAU_MPEG4}, + // map to an arbitrary but existing vdpau format + {IMGFMT_VDPAU, PIX_FMT_VDPAU_H264}, +#endif {0, PIX_FMT_NONE} }; diff --git a/video/img_format.c b/video/img_format.c index c0bbce90ee..9422fbdd27 100644 --- a/video/img_format.c +++ b/video/img_format.c @@ -118,6 +118,7 @@ struct mp_imgfmt_entry mp_imgfmt_list[] = { FMT("vdpau_wmv3", IMGFMT_VDPAU_WMV3) FMT("vdpau_vc1", IMGFMT_VDPAU_VC1) FMT("vdpau_mpeg4", IMGFMT_VDPAU_MPEG4) + FMT("vdpau", IMGFMT_VDPAU) {0} }; diff --git a/video/img_format.h b/video/img_format.h index aac828d580..2438c231b8 100644 --- a/video/img_format.h +++ b/video/img_format.h @@ -242,14 +242,15 @@ enum mp_imgfmt { // Hardware accelerated formats. Plane data points to special data // structures, instead of pixel data. - IMGFMT_VDPAU_MPEG1, + IMGFMT_VDPAU, // new decoder API + IMGFMT_VDPAU_MPEG1, // old API IMGFMT_VDPAU_MPEG2, IMGFMT_VDPAU_H264, IMGFMT_VDPAU_WMV3, IMGFMT_VDPAU_VC1, IMGFMT_VDPAU_MPEG4, - IMGFMT_VDPAU_FIRST = IMGFMT_VDPAU_MPEG1, + IMGFMT_VDPAU_FIRST = IMGFMT_VDPAU, IMGFMT_VDPAU_LAST = IMGFMT_VDPAU_MPEG4, IMGFMT_END, diff --git a/video/out/vo.h b/video/out/vo.h index ae84f9d0c9..3abb8fca58 100644 --- a/video/out/vo.h +++ b/video/out/vo.h @@ -51,8 +51,7 @@ enum mp_voctrl { VOCTRL_GET_EQUALIZER, // struct voctrl_get_equalizer_args* /* for vdpau hardware decoding */ - VOCTRL_HWDEC_DECODER_RENDER, // pointer to hw state - VOCTRL_HWDEC_ALLOC_SURFACE, // struct mp_image** + VOCTRL_GET_HWDEC_INFO, // struct mp_hwdec_info* VOCTRL_NEWFRAME, VOCTRL_SKIPFRAME, diff --git a/video/out/vo_vdpau.c b/video/out/vo_vdpau.c index 6f1483cdd9..414071b5a0 100644 --- a/video/out/vo_vdpau.c +++ b/video/out/vo_vdpau.c @@ -22,10 +22,7 @@ */ /* - * Actual decoding and presentation are implemented here. - * All necessary frame information is collected through - * the "vdpau_render_state" structure after parsing all headers - * etc. in libavcodec for different codecs. + * Actual decoding is done in video/decode/vdpau.c */ #include @@ -36,9 +33,10 @@ #include #include -#include #include "config.h" +#include "video/vdpau.h" +#include "video/decode/dec_video.h" #include "core/mp_msg.h" #include "core/options.h" #include "talloc.h" @@ -56,21 +54,6 @@ ? ((x)+(a)+(m) < (m) ? (x)+(a)+(m) : (x)+(a)) \ : ((x)+(a) < (m) ? (x)+(a) : (x)+(a)-(m))) -#define CHECK_ST_ERROR(message) \ - do { \ - if (vdp_st != VDP_STATUS_OK) { \ - mp_msg(MSGT_VO, MSGL_ERR, "[vdpau] %s: %s\n", \ - message, vdp->get_error_string(vdp_st)); \ - return -1; \ - } \ - } while (0) - -#define CHECK_ST_WARNING(message) \ - do { \ - if (vdp_st != VDP_STATUS_OK) \ - mp_msg(MSGT_VO, MSGL_WARN, "[ vdpau] %s: %s\n", \ - message, vdp->get_error_string(vdp_st)); \ - } while (0) /* number of video and output surfaces */ #define MAX_OUTPUT_SURFACES 15 @@ -84,21 +67,15 @@ * Global variable declaration - VDPAU specific */ -struct vdp_functions { -#define VDP_FUNCTION(vdp_type, _, mp_name) vdp_type *mp_name; -#include "vdpau_template.c" -#undef VDP_FUNCTION -}; - struct vdpctx { - struct vdp_functions *vdp; - + struct mp_vdpau_ctx mpvdp; + struct vdp_functions *vdp; VdpDevice vdp_device; + bool is_preempted; bool preemption_acked; bool preemption_user_notified; double last_preemption_retry_fail; - VdpGetProcAddress *vdp_get_proc_address; VdpPresentationQueueTarget flip_target; VdpPresentationQueue flip_queue; @@ -131,15 +108,18 @@ struct vdpctx { int top_field_first; bool flip; - VdpDecoder decoder; - int decoder_max_refs; - VdpRect src_rect_vid; VdpRect out_rect_vid; struct mp_osd_res osd_rect; - struct vdpau_render_state surface_render[MAX_VIDEO_SURFACES]; - bool surface_in_use[MAX_VIDEO_SURFACES]; + // Surface pool + struct surface_entry { + VdpVideoSurface surface; + int fmt, w, h; + VdpChromaType chroma; + bool in_use; + } video_surfaces[MAX_VIDEO_SURFACES]; + int surface_num; // indexes output_surfaces int query_surface_num; VdpTime recent_vsync_time; @@ -334,7 +314,7 @@ static void add_new_video_surface(struct vo *vo, VdpVideoSurface surface, for (int i = NUM_BUFFERED_VIDEO - 1; i > 0; i--) bv[i] = bv[i - 1]; bv[0] = (struct buffered_video_surface){ - .mpi = reserved_mpi ? mp_image_new_ref(reserved_mpi) : NULL, + .mpi = reserved_mpi, .surface = surface, .pts = pts, }; @@ -419,21 +399,19 @@ static void preemption_callback(VdpDevice device, void *context) { struct vdpctx *vc = context; vc->is_preempted = true; + vc->mpvdp.is_preempted = true; vc->preemption_acked = false; } -/* Initialize vdp_get_proc_address, called from preinit() */ static int win_x11_init_vdpau_procs(struct vo *vo) { struct vo_x11_state *x11 = vo->x11; struct vdpctx *vc = vo->priv; - if (vc->vdp) // reinitialization after preemption - memset(vc->vdp, 0, sizeof(*vc->vdp)); - else - vc->vdp = talloc_zero(vc, struct vdp_functions); struct vdp_functions *vdp = vc->vdp; VdpStatus vdp_st; + *vdp = (struct vdp_functions){0}; + struct vdp_function { const int id; int offset; @@ -448,8 +426,10 @@ static int win_x11_init_vdpau_procs(struct vo *vo) {0, -1} }; + VdpGetProcAddress *get_proc_address; vdp_st = vdp_device_create_x11(x11->display, x11->screen, &vc->vdp_device, - &vc->vdp_get_proc_address); + &get_proc_address); + vc->mpvdp.vdp_device = vc->vdp_device; if (vdp_st != VDP_STATUS_OK) { if (vc->is_preempted) mp_msg(MSGT_VO, MSGL_DBG2, "[vdpau] Error calling " @@ -462,7 +442,7 @@ static int win_x11_init_vdpau_procs(struct vo *vo) vdp->get_error_string = NULL; for (dsc = vdp_func; dsc->offset >= 0; dsc++) { - vdp_st = vc->vdp_get_proc_address(vc->vdp_device, dsc->id, + vdp_st = get_proc_address(vc->vdp_device, dsc->id, (void **)((char *)vdp + dsc->offset)); if (vdp_st != VDP_STATUS_OK) { mp_msg(MSGT_VO, MSGL_ERR, "[vdpau] Error when calling " @@ -671,24 +651,10 @@ static void free_video_specific(struct vo *vo) { struct vdpctx *vc = vo->priv; struct vdp_functions *vdp = vc->vdp; - int i; VdpStatus vdp_st; - if (vc->decoder != VDP_INVALID_HANDLE) - vdp->decoder_destroy(vc->decoder); - vc->decoder = VDP_INVALID_HANDLE; - vc->decoder_max_refs = -1; - forget_frames(vo); - for (i = 0; i < MAX_VIDEO_SURFACES; i++) { - if (vc->surface_render[i].surface != VDP_INVALID_HANDLE) { - vdp_st = vdp->video_surface_destroy(vc->surface_render[i].surface); - CHECK_ST_WARNING("Error when calling vdp_video_surface_destroy"); - } - vc->surface_render[i].surface = VDP_INVALID_HANDLE; - } - if (vc->video_mixer != VDP_INVALID_HANDLE) { vdp_st = vdp->video_mixer_destroy(vc->video_mixer); CHECK_ST_WARNING("Error when calling vdp_video_mixer_destroy"); @@ -702,73 +668,13 @@ static void free_video_specific(struct vo *vo) vc->screenshot_surface = VDP_INVALID_HANDLE; } -static int create_vdp_decoder(struct vo *vo, int max_refs) -{ - struct vdpctx *vc = vo->priv; - struct vdp_functions *vdp = vc->vdp; - VdpStatus vdp_st; - VdpDecoderProfile vdp_decoder_profile; - if (vc->decoder != VDP_INVALID_HANDLE) - vdp->decoder_destroy(vc->decoder); - switch (vc->image_format) { - case IMGFMT_VDPAU_MPEG1: - vdp_decoder_profile = VDP_DECODER_PROFILE_MPEG1; - break; - case IMGFMT_VDPAU_MPEG2: - vdp_decoder_profile = VDP_DECODER_PROFILE_MPEG2_MAIN; - break; - case IMGFMT_VDPAU_H264: - vdp_decoder_profile = VDP_DECODER_PROFILE_H264_HIGH; - mp_msg(MSGT_VO, MSGL_V, "[vdpau] Creating H264 hardware decoder " - "for %d reference frames.\n", max_refs); - break; - case IMGFMT_VDPAU_WMV3: - vdp_decoder_profile = VDP_DECODER_PROFILE_VC1_MAIN; - break; - case IMGFMT_VDPAU_VC1: - vdp_decoder_profile = VDP_DECODER_PROFILE_VC1_ADVANCED; - break; - case IMGFMT_VDPAU_MPEG4: - vdp_decoder_profile = VDP_DECODER_PROFILE_MPEG4_PART2_ASP; - break; - default: - mp_msg(MSGT_VO, MSGL_ERR, "[vdpau] Unknown image format!\n"); - goto fail; - } - vdp_st = vdp->decoder_create(vc->vdp_device, vdp_decoder_profile, - vc->vid_width, vc->vid_height, max_refs, - &vc->decoder); - CHECK_ST_WARNING("Failed creating VDPAU decoder"); - if (vdp_st != VDP_STATUS_OK) { - fail: - vc->decoder = VDP_INVALID_HANDLE; - vc->decoder_max_refs = 0; - return 0; - } - vc->decoder_max_refs = max_refs; - return 1; -} - static int initialize_vdpau_objects(struct vo *vo) { struct vdpctx *vc = vo->priv; - vc->vdp_chroma_type = VDP_CHROMA_TYPE_420; - switch (vc->image_format) { - case IMGFMT_420P: - vc->vdp_pixel_format = VDP_YCBCR_FORMAT_YV12; - break; - case IMGFMT_NV12: - vc->vdp_pixel_format = VDP_YCBCR_FORMAT_NV12; - break; - case IMGFMT_YUYV: - vc->vdp_pixel_format = VDP_YCBCR_FORMAT_YUYV; - vc->vdp_chroma_type = VDP_CHROMA_TYPE_422; - break; - case IMGFMT_UYVY: - vc->vdp_pixel_format = VDP_YCBCR_FORMAT_UYVY; - vc->vdp_chroma_type = VDP_CHROMA_TYPE_422; - } + mp_vdpau_get_format(vc->image_format, &vc->vdp_chroma_type, + &vc->vdp_pixel_format); + if (win_x11_init_vdpau_flip_queue(vo) < 0) return -1; @@ -784,9 +690,8 @@ static void mark_vdpau_objects_uninitialized(struct vo *vo) { struct vdpctx *vc = vo->priv; - vc->decoder = VDP_INVALID_HANDLE; for (int i = 0; i < MAX_VIDEO_SURFACES; i++) - vc->surface_render[i].surface = VDP_INVALID_HANDLE; + vc->video_surfaces[i].surface = VDP_INVALID_HANDLE; forget_frames(vo); vc->video_mixer = VDP_INVALID_HANDLE; vc->flip_queue = VDP_INVALID_HANDLE; @@ -795,6 +700,7 @@ static void mark_vdpau_objects_uninitialized(struct vo *vo) vc->output_surfaces[i] = VDP_INVALID_HANDLE; vc->screenshot_surface = VDP_INVALID_HANDLE; vc->vdp_device = VDP_INVALID_HANDLE; + vc->mpvdp.vdp_device = vc->vdp_device; for (int i = 0; i < MAX_OSD_PARTS; i++) { struct osd_bitmap_surface *sfc = &vc->osd_surfaces[i]; talloc_free(sfc->packer); @@ -831,11 +737,24 @@ static int handle_preemption(struct vo *vo) } vc->last_preemption_retry_fail = 0; vc->is_preempted = false; + vc->mpvdp.is_preempted = false; + vc->mpvdp.preemption_counter++; vc->preemption_user_notified = false; mp_tmsg(MSGT_VO, MSGL_INFO, "[vdpau] Recovered from display preemption.\n"); return 1; } +static bool status_ok(struct vo *vo) +{ + return vo->config_ok && handle_preemption(vo) >= 0; +} + +static bool ctx_status_ok(struct mp_vdpau_ctx *ctx) +{ + struct vo *vo = ctx->priv; + return handle_preemption(vo) >= 0; +} + /* * connect to X server, create and map window, initialize all * VDPAU objects, create different surfaces etc. @@ -855,8 +774,6 @@ static int config(struct vo *vo, uint32_t width, uint32_t height, vc->vid_height = height; free_video_specific(vo); - if (IMGFMT_IS_VDPAU(vc->image_format) && !create_vdp_decoder(vo, 2)) - return -1; vo_x11_config_vo_window(vo, NULL, vo->dx, vo->dy, d_width, d_height, flags, "vdpau"); @@ -1191,51 +1108,82 @@ static void flip_page_timed(struct vo *vo, int64_t pts_us, int duration) vc->surface_num = WRAP_ADD(vc->surface_num, 1, vc->num_output_surfaces); } -static int decoder_render(struct vo *vo, void *state_ptr) +static void release_decoder_surface(void *ptr) { + bool *in_use_ptr = ptr; + *in_use_ptr = false; +} + +static struct mp_image *create_ref(struct surface_entry *e) +{ + assert(!e->in_use); + e->in_use = true; + struct mp_image *res = + mp_image_new_custom_ref(&(struct mp_image){0}, &e->in_use, + release_decoder_surface); + mp_image_setfmt(res, e->fmt); + mp_image_set_size(res, e->w, e->h); + res->planes[0] = (void *)"dummy"; // must be non-NULL, otherwise arbitrary + res->planes[3] = (void *)(intptr_t)e->surface; + return res; +} + +static struct mp_image *get_video_surface(struct mp_vdpau_ctx *ctx, int fmt, + VdpChromaType chroma, int w, int h) +{ + struct vo *vo = ctx->priv; struct vdpctx *vc = vo->priv; struct vdp_functions *vdp = vc->vdp; VdpStatus vdp_st; - struct vdpau_render_state *rndr = (struct vdpau_render_state *)state_ptr; - if (handle_preemption(vo) < 0) - return VO_TRUE; + assert(IMGFMT_IS_VDPAU(fmt)); - int max_refs = vc->image_format == IMGFMT_VDPAU_H264 ? - rndr->info.h264.num_ref_frames : 2; - if (!IMGFMT_IS_VDPAU(vc->image_format)) - return VO_FALSE; - if ((vc->decoder == VDP_INVALID_HANDLE || vc->decoder_max_refs < max_refs) - && !create_vdp_decoder(vo, max_refs)) - return VO_FALSE; - - vdp_st = vdp->decoder_render(vc->decoder, rndr->surface, - (void *)&rndr->info, - rndr->bitstream_buffers_used, - rndr->bitstream_buffers); - CHECK_ST_WARNING("Failed VDPAU decoder rendering"); - return VO_TRUE; -} - - -static struct vdpau_render_state *get_surface(struct vo *vo, int number) -{ - struct vdpctx *vc = vo->priv; - struct vdp_functions *vdp = vc->vdp; - - if (number >= MAX_VIDEO_SURFACES) - return NULL; - if (vc->surface_render[number].surface == VDP_INVALID_HANDLE - && !vc->is_preempted) { - VdpStatus vdp_st; - vdp_st = vdp->video_surface_create(vc->vdp_device, vc->vdp_chroma_type, - vc->vid_width, vc->vid_height, - &vc->surface_render[number].surface); - CHECK_ST_WARNING("Error when calling vdp_video_surface_create"); + // Destroy all unused surfaces that don't have matching parameters + for (int n = 0; n < MAX_VIDEO_SURFACES; n++) { + struct surface_entry *e = &vc->video_surfaces[n]; + if (!e->in_use && e->surface != VDP_INVALID_HANDLE) { + if (e->fmt != fmt || e->chroma != chroma || e->w != w || e->h != h) { + vdp_st = vdp->video_surface_destroy(e->surface); + CHECK_ST_WARNING("Error when calling vdp_video_surface_destroy"); + e->surface = VDP_INVALID_HANDLE; + } + } } - mp_msg(MSGT_VO, MSGL_DBG3, "vdpau vid create: %u\n", - vc->surface_render[number].surface); - return &vc->surface_render[number]; + + // Try to find an existing unused surface + for (int n = 0; n < MAX_VIDEO_SURFACES; n++) { + struct surface_entry *e = &vc->video_surfaces[n]; + if (!e->in_use && e->surface != VDP_INVALID_HANDLE) { + assert(e->w == w && e->h == h); + assert(e->fmt == fmt && e->chroma == chroma); + return create_ref(e); + } + } + + // Allocate new surface + for (int n = 0; n < MAX_VIDEO_SURFACES; n++) { + struct surface_entry *e = &vc->video_surfaces[n]; + if (!e->in_use) { + assert(e->surface == VDP_INVALID_HANDLE); + e->fmt = fmt; + e->chroma = chroma; + e->w = w; + e->h = h; + if (vc->is_preempted) { + mp_msg(MSGT_VO, MSGL_WARN, "[vdpau] Preempted, no surface.\n"); + } else { + vdp_st = vdp->video_surface_create(vc->vdp_device, chroma, + w, h, &e->surface); + CHECK_ST_WARNING("Error when calling vdp_video_surface_create"); + } + return create_ref(e); + } + } + + mp_msg(MSGT_VO, MSGL_ERR, "[vdpau] no surfaces available in " + "get_video_surface\n"); + // TODO: this probably breaks things forever, provide a dummy buffer? + return NULL; } static void draw_image(struct vo *vo, mp_image_t *mpi) @@ -1243,13 +1191,17 @@ static void draw_image(struct vo *vo, mp_image_t *mpi) struct vdpctx *vc = vo->priv; struct vdp_functions *vdp = vc->vdp; struct mp_image *reserved_mpi = NULL; - struct vdpau_render_state *rndr; + VdpVideoSurface surface = VDP_INVALID_HANDLE; if (IMGFMT_IS_VDPAU(vc->image_format)) { - rndr = (struct vdpau_render_state *)mpi->planes[0]; - reserved_mpi = mpi; + surface = (VdpVideoSurface)(intptr_t)mpi->planes[3]; + reserved_mpi = mp_image_new_ref(mpi); } else { - rndr = get_surface(vo, vc->deint_counter); + reserved_mpi = get_video_surface(&vc->mpvdp, IMGFMT_VDPAU, + vc->vdp_chroma_type, mpi->w, mpi->h); + if (!reserved_mpi) + return; + surface = (VdpVideoSurface)(intptr_t)reserved_mpi->planes[3]; vc->deint_counter = WRAP_ADD(vc->deint_counter, 1, NUM_BUFFERED_VIDEO); if (handle_preemption(vo) >= 0) { VdpStatus vdp_st; @@ -1257,7 +1209,7 @@ static void draw_image(struct vo *vo, mp_image_t *mpi) mpi->planes[1]}; if (vc->image_format == IMGFMT_NV12) destdata[1] = destdata[2]; - vdp_st = vdp->video_surface_put_bits_y_cb_cr(rndr->surface, + vdp_st = vdp->video_surface_put_bits_y_cb_cr(surface, vc->vdp_pixel_format, destdata, mpi->stride); CHECK_ST_WARNING("Error when calling " "vdp_video_surface_put_bits_y_cb_cr"); @@ -1268,7 +1220,7 @@ static void draw_image(struct vo *vo, mp_image_t *mpi) else vc->top_field_first = 1; - add_new_video_surface(vo, rndr->surface, reserved_mpi, mpi->pts); + add_new_video_surface(vo, surface, reserved_mpi, mpi->pts); return; } @@ -1331,58 +1283,12 @@ static struct mp_image *get_window_screenshot(struct vo *vo) return image; } -static void release_decoder_surface(void *ptr) -{ - bool *in_use_ptr = ptr; - *in_use_ptr = false; -} - -static struct mp_image *get_decoder_surface(struct vo *vo) -{ - struct vdpctx *vc = vo->priv; - - if (!IMGFMT_IS_VDPAU(vc->image_format)) - return NULL; - - for (int n = 0; n < MAX_VIDEO_SURFACES; n++) { - if (!vc->surface_in_use[n]) { - vc->surface_in_use[n] = true; - struct mp_image *res = - mp_image_new_custom_ref(&(struct mp_image){0}, - &vc->surface_in_use[n], - release_decoder_surface); - mp_image_setfmt(res, vc->image_format); - mp_image_set_size(res, vc->vid_width, vc->vid_height); - struct vdpau_render_state *rndr = get_surface(vo, n); - res->planes[0] = (void *)rndr; - return res; - } - } - - mp_msg(MSGT_VO, MSGL_ERR, "[vdpau] no surfaces available in " - "get_decoder_surface\n"); - // TODO: this probably breaks things forever, provide a dummy buffer? - return NULL; -} static int query_format(struct vo *vo, uint32_t format) { - int default_flags = VFCAP_CSP_SUPPORTED | VFCAP_CSP_SUPPORTED_BY_HW - | VFCAP_FLIP; - switch (format) { - case IMGFMT_420P: - case IMGFMT_NV12: - case IMGFMT_YUYV: - case IMGFMT_UYVY: - case IMGFMT_VDPAU_MPEG1: - case IMGFMT_VDPAU_MPEG2: - case IMGFMT_VDPAU_H264: - case IMGFMT_VDPAU_WMV3: - case IMGFMT_VDPAU_VC1: - case IMGFMT_VDPAU_MPEG4: - return default_flags; - } - return 0; + if (!mp_vdpau_get_format(format, NULL, NULL)) + return 0; + return VFCAP_CSP_SUPPORTED | VFCAP_CSP_SUPPORTED_BY_HW | VFCAP_FLIP; } static void destroy_vdpau_objects(struct vo *vo) @@ -1394,6 +1300,15 @@ static void destroy_vdpau_objects(struct vo *vo) free_video_specific(vo); + for (int i = 0; i < MAX_VIDEO_SURFACES; i++) { + // can't hold references past VO lifetime + assert(!vc->video_surfaces[i].in_use); + if (vc->video_surfaces[i].surface != VDP_INVALID_HANDLE) { + vdp_st = vdp->video_surface_destroy(vc->video_surfaces[i].surface); + CHECK_ST_WARNING("Error when calling vdp_video_surface_destroy"); + } + } + if (vc->flip_queue != VDP_INVALID_HANDLE) { vdp_st = vdp->presentation_queue_destroy(vc->flip_queue); CHECK_ST_WARNING("Error when calling vdp_presentation_queue_destroy"); @@ -1426,22 +1341,22 @@ static void destroy_vdpau_objects(struct vo *vo) static void uninit(struct vo *vo) { - struct vdpctx *vc = vo->priv; - /* Destroy all vdpau objects */ destroy_vdpau_objects(vo); vo_x11_uninit(vo); - - // Free bitstream buffers allocated by FFmpeg - for (int i = 0; i < MAX_VIDEO_SURFACES; i++) - av_freep(&vc->surface_render[i].bitstream_buffers); } static int preinit(struct vo *vo) { struct vdpctx *vc = vo->priv; + vc->vdp = talloc_zero(vc, struct vdp_functions); + vc->mpvdp.priv = vo; + vc->mpvdp.vdp = vc->vdp; + vc->mpvdp.status_ok = ctx_status_ok; + vc->mpvdp.get_video_surface = get_video_surface; + // Mark everything as invalid first so uninit() can tell what has been // allocated mark_vdpau_objects_uninitialized(vo); @@ -1459,7 +1374,7 @@ static int preinit(struct vo *vo) // After this calling uninit() should work to free resources if (win_x11_init_vdpau_procs(vo) < 0) { - if (vc->vdp && vc->vdp->device_destroy) + if (vc->vdp->device_destroy) vc->vdp->device_destroy(vc->vdp_device); vo_x11_uninit(vo); return -1; @@ -1475,13 +1390,6 @@ static int get_equalizer(struct vo *vo, const char *name, int *value) VO_TRUE : VO_NOTIMPL; } -static bool status_ok(struct vo *vo) -{ - if (!vo->config_ok || handle_preemption(vo) < 0) - return false; - return true; -} - static int set_equalizer(struct vo *vo, const char *name, int value) { struct vdpctx *vc = vo->priv; @@ -1534,11 +1442,11 @@ static int control(struct vo *vo, uint32_t request, void *data) if (vc->dropped_frame) vo->want_redraw = true; return true; - case VOCTRL_HWDEC_ALLOC_SURFACE: - *(struct mp_image **)data = get_decoder_surface(vo); + case VOCTRL_GET_HWDEC_INFO: { + struct mp_hwdec_info *arg = data; + arg->vdpau_ctx = &vc->mpvdp; return true; - case VOCTRL_HWDEC_DECODER_RENDER: - return decoder_render(vo, data); + } case VOCTRL_GET_PANSCAN: return VO_TRUE; case VOCTRL_SET_PANSCAN: diff --git a/video/vdpau.c b/video/vdpau.c new file mode 100644 index 0000000000..697bda8447 --- /dev/null +++ b/video/vdpau.c @@ -0,0 +1,73 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with mpv. If not, see . + */ + +#include "vdpau.h" + +#include "video/img_format.h" + +// Check whether vdpau initialization and preemption status is ok and we can +// proceed normally. +bool mp_vdpau_status_ok(struct mp_vdpau_ctx *ctx) +{ + return ctx->status_ok(ctx); +} + +struct mp_image *mp_vdpau_get_video_surface(struct mp_vdpau_ctx *ctx, int fmt, + VdpChromaType chroma, int w, int h) +{ + return ctx->get_video_surface(ctx, fmt, chroma, w, h); +} + +bool mp_vdpau_get_format(int imgfmt, VdpChromaType *out_chroma_type, + VdpYCbCrFormat *out_pixel_format) +{ + VdpChromaType chroma = VDP_CHROMA_TYPE_420; + VdpYCbCrFormat ycbcr = (VdpYCbCrFormat)-1; + + switch (imgfmt) { + case IMGFMT_420P: + ycbcr = VDP_YCBCR_FORMAT_YV12; + break; + case IMGFMT_NV12: + ycbcr = VDP_YCBCR_FORMAT_NV12; + break; + case IMGFMT_YUYV: + ycbcr = VDP_YCBCR_FORMAT_YUYV; + chroma = VDP_CHROMA_TYPE_422; + break; + case IMGFMT_UYVY: + ycbcr = VDP_YCBCR_FORMAT_UYVY; + chroma = VDP_CHROMA_TYPE_422; + break; + case IMGFMT_VDPAU_MPEG1: + case IMGFMT_VDPAU_MPEG2: + case IMGFMT_VDPAU_H264: + case IMGFMT_VDPAU_WMV3: + case IMGFMT_VDPAU_VC1: + case IMGFMT_VDPAU_MPEG4: + case IMGFMT_VDPAU: + break; + default: + return false; + } + + if (out_chroma_type) + *out_chroma_type = chroma; + if (out_pixel_format) + *out_pixel_format = ycbcr; + return true; +} diff --git a/video/vdpau.h b/video/vdpau.h new file mode 100644 index 0000000000..c8a7288b66 --- /dev/null +++ b/video/vdpau.h @@ -0,0 +1,55 @@ +#ifndef MPV_VDPAU_H +#define MPV_VDPAU_H + +#include +#include + +#include +#include + +#include "core/mp_msg.h" + +#define CHECK_ST_ERROR(message) \ + do { \ + if (vdp_st != VDP_STATUS_OK) { \ + mp_msg(MSGT_VO, MSGL_ERR, "[vdpau] %s: %s\n", \ + message, vdp->get_error_string(vdp_st)); \ + return -1; \ + } \ + } while (0) + +#define CHECK_ST_WARNING(message) \ + do { \ + if (vdp_st != VDP_STATUS_OK) \ + mp_msg(MSGT_VO, MSGL_WARN, "[vdpau] %s: %s\n", \ + message, vdp->get_error_string(vdp_st)); \ + } while (0) + +struct vdp_functions { +#define VDP_FUNCTION(vdp_type, _, mp_name) vdp_type *mp_name; +#include "video/out/vdpau_template.c" +#undef VDP_FUNCTION +}; + +// Shared state. Objects created from different VdpDevices are often (always?) +// incompatible to each other, so all code must use a shared VdpDevice. +struct mp_vdpau_ctx { + struct vdp_functions *vdp; + VdpDevice vdp_device; + bool is_preempted; // set to true during unavailability + uint64_t preemption_counter; // incremented after _restoring_ + bool (*status_ok)(struct mp_vdpau_ctx *ctx); + struct mp_image *(*get_video_surface)(struct mp_vdpau_ctx *ctx, int fmt, + VdpChromaType chroma, int w, int h); + void *priv; // for VO +}; + +bool mp_vdpau_status_ok(struct mp_vdpau_ctx *ctx); + +struct mp_image *mp_vdpau_get_video_surface(struct mp_vdpau_ctx *ctx, int fmt, + VdpChromaType chroma, int w, int h); + +bool mp_vdpau_get_format(int imgfmt, VdpChromaType *out_chroma_type, + VdpYCbCrFormat *out_pixel_format); + +#endif