diff --git a/etc/example.conf b/etc/example.conf index e610b81b4f..96c7afaca0 100644 --- a/etc/example.conf +++ b/etc/example.conf @@ -81,6 +81,9 @@ # Use this for a widescreen monitor, non-square pixels. #monitoraspect=16:9 +# Enable hardware decoding if available. Often, this requires using an certain +# video output, otherwise no hardware decoding will be used. +#hwdec = auto ############ # Profiles # @@ -89,7 +92,11 @@ # The options declared as part of profiles override global default settings, # but only take effect when the profile is active. -#[vo.vdpau] +# The following profile can be enabled on the command line with: --profile=vdpau + +#[vdpau] +# The profile forces the vdpau VO. +#vo=vdpau # Use hardware decoding (this might break playback of some h264 files) #hwdec=vdpau # Most video filters do not work with vdpau. diff --git a/mpvcore/options.c b/mpvcore/options.c index b66556d8bd..ff278cc7a2 100644 --- a/mpvcore/options.c +++ b/mpvcore/options.c @@ -466,6 +466,7 @@ const m_option_t mp_opts[] = { OPT_CHOICE("hwdec", hwdec_api, 0, ({"no", 0}, + {"auto", -1}, {"vdpau", 1}, {"vda", 2}, {"crystalhd", 3})), diff --git a/video/decode/lavc.h b/video/decode/lavc.h index 3611530400..94973cb2d8 100644 --- a/video/decode/lavc.h +++ b/video/decode/lavc.h @@ -10,10 +10,19 @@ #include "demux/stheader.h" #include "video/mp_image.h" +// keep in sync with --hwdec option +enum hwdec_type { + HWDEC_AUTO = -1, + HWDEC_NONE = 0, + HWDEC_VDPAU = 1, + HWDEC_VDA = 2, + HWDEC_CRYSTALHD = 3, +}; + typedef struct lavc_ctx { AVCodecContext *avctx; AVFrame *pic; - struct hwdec *hwdec; + struct vd_lavc_hwdec *hwdec; enum PixelFormat pix_fmt; int do_hw_dr1; int vo_initialized; @@ -35,16 +44,31 @@ typedef struct lavc_ctx { 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. +struct vd_lavc_hwdec { + enum hwdec_type type; + // If non-NULL: lists pairs software and hardware decoders. If the current + // codec is not one of the listed software decoders, probing fails. + // Otherwise, the AVCodecContext is initialized with the associated + // hardware decoder. + // Useful only if hw decoding requires a special codec, instead of using + // the libavcodec hwaccel infrastructure. + const char **codec_pairs; + // If not-NULL: a 0 terminated list of IMGFMT_ formats, and only one of + // these formats is accepted in the libavcodec get_format callback. const int *image_formats; + int (*probe)(struct vd_lavc_hwdec *hwdec, struct mp_hwdec_info *info, + const char *decoder); 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); }; +enum { + HWDEC_ERR_NO_CTX = -2, + HWDEC_ERR_NO_CODEC = -3, +}; + // 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/vd_lavc.c b/video/decode/vd_lavc.c index 0367c2afed..2fc7a1ea4c 100644 --- a/video/decode/vd_lavc.c +++ b/video/decode/vd_lavc.c @@ -59,7 +59,8 @@ #include "mpvcore/m_option.h" -static void init_avctx(sh_video_t *sh, const char *decoder, struct hwdec *hwdec); +static void init_avctx(sh_video_t *sh, const char *decoder, + struct vd_lavc_hwdec *hwdec); static void uninit_avctx(sh_video_t *sh); static void setup_refcounting_hw(struct AVCodecContext *s); @@ -81,72 +82,56 @@ const m_option_t lavc_decode_opts_conf[] = { {NULL, NULL, 0, 0, 0, 0, NULL} }; -// keep in sync with --hwdec option -enum hwdec_type { - HWDEC_NONE = 0, - HWDEC_VDPAU = 1, - HWDEC_VDA = 2, - HWDEC_CRYSTALHD = 3, +const struct vd_lavc_hwdec mp_vd_lavc_vdpau; +const struct vd_lavc_hwdec mp_vd_lavc_vdpau_old; + +static const struct vd_lavc_hwdec mp_vd_lavc_crystalhd = { + .type = HWDEC_CRYSTALHD, + .codec_pairs = (const char *[]) { + "mpeg2", "mpeg2_crystalhd", + "msmpeg4", "msmpeg4_crystalhd", + "wmv3", "wmv3_crystalhd", + "vc1", "vc1_crystalhd", + "h264", "h264_crystalhd", + "mpeg4", "mpeg4_crystalhd", + NULL + }, }; -struct hwdec { - enum hwdec_type api; - const char *codec, *hw_codec; - const struct vd_lavc_hwdec_functions *fns; +static const struct vd_lavc_hwdec mp_vd_lavc_vda = { + .type = HWDEC_VDA, + .codec_pairs = (const char *[]) {"h264", "h264_vda", NULL}, }; -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[] = { +static const struct vd_lavc_hwdec *hwdec_list[] = { #if 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}, + &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}, + &mp_vd_lavc_vdpau_old, #endif #endif // CONFIG_VDPAU - - {HWDEC_VDA, "h264", "h264_vda"}, - - {HWDEC_CRYSTALHD, "mpeg2", "mpeg2_crystalhd"}, - {HWDEC_CRYSTALHD, "msmpeg4", "msmpeg4_crystalhd"}, - {HWDEC_CRYSTALHD, "wmv3", "wmv3_crystalhd"}, - {HWDEC_CRYSTALHD, "vc1", "vc1_crystalhd"}, - {HWDEC_CRYSTALHD, "h264", "h264_crystalhd"}, - {HWDEC_CRYSTALHD, "mpeg4", "mpeg4_crystalhd"}, - - {0} + &mp_vd_lavc_vda, + &mp_vd_lavc_crystalhd, + NULL }; -static struct hwdec *find_hwcodec(enum hwdec_type api, const char *codec) +static struct vd_lavc_hwdec *find_hwcodec(enum hwdec_type api) { - for (int n = 0; hwdec_list[n].api; n++) { - if (hwdec_list[n].api == api && strcmp(hwdec_list[n].codec, codec) == 0) - return (struct hwdec *)&hwdec_list[n]; + for (int n = 0; hwdec_list[n]; n++) { + if (hwdec_list[n]->type == api) + return (struct vd_lavc_hwdec *)hwdec_list[n]; } return NULL; } -static bool hwdec_codec_allowed(sh_video_t *sh, struct hwdec *hwdec) +static bool hwdec_codec_allowed(sh_video_t *sh, const char *codec) { bstr s = bstr0(sh->opts->hwdec_codecs); while (s.len) { bstr item; bstr_split_tok(s, ",", &item, &s); - if (bstr_equals0(item, "all") || bstr_equals0(item, hwdec->codec)) + if (bstr_equals0(item, "all") || bstr_equals0(item, codec)) return true; } return false; @@ -165,6 +150,56 @@ static enum AVDiscard str2AVDiscard(char *str) return AVDISCARD_DEFAULT; } +static int hwdec_probe(struct vd_lavc_hwdec *hwdec, struct mp_hwdec_info *info, + const char *decoder, const char **hw_decoder) +{ + if (hwdec->codec_pairs) { + for (int n = 0; hwdec->codec_pairs[n + 0]; n += 2) { + const char *sw = hwdec->codec_pairs[n + 0]; + const char *hw = hwdec->codec_pairs[n + 1]; + if (decoder && strcmp(decoder, sw) == 0) { + AVCodec *codec = avcodec_find_decoder_by_name(hw); + *hw_decoder = hw; + if (codec) + goto found; + } + } + return HWDEC_ERR_NO_CODEC; + found: ; + } + int r = 0; + if (hwdec->probe) + r = hwdec->probe(hwdec, info, decoder); + return r; +} + +static bool probe_hwdec(sh_video_t *sh, bool autoprobe, enum hwdec_type api, + const char *decoder, struct vd_lavc_hwdec **use_hwdec, + const char **use_decoder) +{ + struct vd_lavc_hwdec *hwdec = find_hwcodec(api); + if (!hwdec) { + mp_tmsg(MSGT_DECVIDEO, MSGL_V, "Requested hardware decoder not " + "compiled.\n"); + return false; + } + const char *hw_decoder = NULL; + int r = hwdec_probe(hwdec, sh->hwdec_info, decoder, &hw_decoder); + if (r >= 0) { + *use_hwdec = hwdec; + *use_decoder = hw_decoder; + return true; + } else if (r == HWDEC_ERR_NO_CODEC) { + mp_tmsg(MSGT_DECVIDEO, MSGL_V, "Hardware decoder '%s' not found in " + "libavcodec.\n", hw_decoder ? hw_decoder : decoder); + } else if (r == HWDEC_ERR_NO_CTX && !autoprobe) { + mp_tmsg(MSGT_DECVIDEO, MSGL_WARN, "VO does not support requested " + "hardware decoder.\n"); + } + return false; +} + + static int init(sh_video_t *sh, const char *decoder) { vd_ffmpeg_ctx *ctx; @@ -182,29 +217,35 @@ static int init(sh_video_t *sh, const char *decoder) return 0; } - struct hwdec *hwdec = find_hwcodec(sh->opts->hwdec_api, decoder); - struct hwdec *use_hwdec = NULL; - if (hwdec && hwdec_codec_allowed(sh, 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); + struct vd_lavc_hwdec *hwdec = NULL; + const char *hw_decoder = NULL; + + if (hwdec_codec_allowed(sh, decoder)) { + if (sh->opts->hwdec_api == HWDEC_AUTO) { + for (int n = 0; hwdec_list[n]; n++) { + if (probe_hwdec(sh, true, hwdec_list[n]->type, decoder, + &hwdec, &hw_decoder)) + break; } - } else { - ctx->software_fallback_decoder = talloc_strdup(ctx, decoder); - use_hwdec = hwdec; + } else if (sh->opts->hwdec_api != HWDEC_NONE) { + probe_hwdec(sh, false, sh->opts->hwdec_api, decoder, + &hwdec, &hw_decoder); } - } else if (!hwdec && sh->opts->hwdec_api) { - mp_tmsg(MSGT_DECVIDEO, MSGL_WARN, "Selected hardware decoding API not " - "available, using software decoding.\n"); + } else { + mp_tmsg(MSGT_DECVIDEO, MSGL_V, "Not trying to use hardware decoding: " + "codec %s is blacklisted by user.\n", decoder); } - init_avctx(sh, decoder, use_hwdec); + if (hwdec) { + ctx->software_fallback_decoder = talloc_strdup(ctx, decoder); + if (hw_decoder) + decoder = hw_decoder; + mp_tmsg(MSGT_DECVIDEO, MSGL_INFO, "Trying to use hardware decoding.\n"); + } else if (sh->opts->hwdec_api != HWDEC_NONE) { + mp_tmsg(MSGT_DECVIDEO, MSGL_INFO, "Using software decoding.\n"); + } + + init_avctx(sh, decoder, hwdec); if (!ctx->avctx) { if (ctx->software_fallback_decoder) { mp_tmsg(MSGT_DECVIDEO, MSGL_ERR, "Error initializing hardware " @@ -279,7 +320,8 @@ static void set_from_bih(AVCodecContext *avctx, uint32_t format, avctx->coded_height = bih->biHeight; } -static void init_avctx(sh_video_t *sh, const char *decoder, struct hwdec *hwdec) +static void init_avctx(sh_video_t *sh, const char *decoder, + struct vd_lavc_hwdec *hwdec) { vd_ffmpeg_ctx *ctx = sh->context; struct lavc_param *lavc_param = &sh->opts->lavc_param; @@ -311,13 +353,13 @@ static void init_avctx(sh_video_t *sh, const char *decoder, struct hwdec *hwdec) avctx->thread_count = lavc_param->threads; - if (ctx->hwdec && ctx->hwdec->fns) { + if (ctx->hwdec && ctx->hwdec->allocate_image) { ctx->do_hw_dr1 = true; avctx->thread_count = 1; - if (ctx->hwdec->fns->image_formats) + if (ctx->hwdec->image_formats) avctx->get_format = get_format_hwdec; setup_refcounting_hw(avctx); - if (ctx->hwdec->fns->init(ctx) < 0) { + if (ctx->hwdec->init && ctx->hwdec->init(ctx) < 0) { uninit_avctx(sh); return; } @@ -404,8 +446,8 @@ 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 (ctx->hwdec && ctx->hwdec->uninit) + ctx->hwdec->uninit(ctx); #if !HAVE_AVUTIL_REFCOUNTING mp_buffer_pool_free(&ctx->dr1_buffer_pool); @@ -487,10 +529,10 @@ 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 && ctx->hwdec->fns); + assert(ctx->hwdec); for (int i = 0; fmt[i] != PIX_FMT_NONE; i++) { - const int *okfmt = ctx->hwdec->fns->image_formats; + const int *okfmt = ctx->hwdec->image_formats; for (int n = 0; okfmt && okfmt[n]; n++) { if (imgfmt2pixfmt(okfmt[n]) == fmt[i]) return fmt[i]; @@ -519,7 +561,7 @@ static struct mp_image *get_surface_hwdec(struct sh_video *sh, AVFrame *pic) if (!IMGFMT_IS_HWACCEL(imgfmt)) return NULL; - struct mp_image *mpi = ctx->hwdec->fns->allocate_image(ctx, pic); + struct mp_image *mpi = ctx->hwdec->allocate_image(ctx, pic); if (mpi) { for (int i = 0; i < 4; i++) @@ -694,8 +736,8 @@ 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); + if (ctx->hwdec && ctx->hwdec->fix_image) + ctx->hwdec->fix_image(ctx, mpi); mpi->colorspace = ctx->image_params.colorspace; mpi->levels = ctx->image_params.colorlevels; diff --git a/video/decode/vdpau.c b/video/decode/vdpau.c index b259ae303a..e20abdafca 100644 --- a/video/decode/vdpau.c +++ b/video/decode/vdpau.c @@ -23,6 +23,7 @@ #include "lavc.h" #include "mpvcore/mp_common.h" +#include "mpvcore/av_common.h" #include "video/fmt-conversion.h" #include "video/vdpau.h" #include "video/decode/dec_video.h" @@ -109,6 +110,19 @@ static int handle_preemption(struct lavc_ctx *ctx) return 0; } +static const struct profile_entry *find_codec(enum AVCodecID id, int ff_profile) +{ + for (int n = 0; n < MP_ARRAY_SIZE(profiles); n++) { + if (profiles[n].av_codec == id && + (profiles[n].ff_profile == ff_profile || + profiles[n].ff_profile == FF_PROFILE_UNKNOWN)) + { + return &profiles[n]; + } + } + return NULL; +} + static bool create_vdp_decoder(struct lavc_ctx *ctx) { struct priv *p = ctx->hwdec_priv; @@ -121,17 +135,8 @@ static bool create_vdp_decoder(struct lavc_ctx *ctx) 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; - } - } - + const struct profile_entry *pe = find_codec(ctx->avctx->codec_id, + ctx->avctx->profile); if (!pe) { mp_msg(MSGT_VO, MSGL_ERR, "[vdpau] Unknown codec!\n"); goto fail; @@ -200,9 +205,6 @@ static void uninit(struct lavc_ctx *ctx) 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, @@ -220,8 +222,20 @@ static int init(struct lavc_ctx *ctx) return 0; } -const struct vd_lavc_hwdec_functions mp_vd_lavc_vdpau = { +static int probe(struct vd_lavc_hwdec *hwdec, struct mp_hwdec_info *info, + const char *decoder) +{ + if (!info || !info->vdpau_ctx) + return HWDEC_ERR_NO_CTX; + if (!find_codec(mp_codec_to_av_codec_id(decoder), FF_PROFILE_UNKNOWN)) + return HWDEC_ERR_NO_CODEC; + return 0; +} + +const struct vd_lavc_hwdec mp_vd_lavc_vdpau = { + .type = HWDEC_VDPAU, .image_formats = (const int[]) {IMGFMT_VDPAU, 0}, + .probe = probe, .init = init, .uninit = uninit, .allocate_image = allocate_image, diff --git a/video/decode/vdpau_old.c b/video/decode/vdpau_old.c index e9c88b69ea..4abdafa9c0 100644 --- a/video/decode/vdpau_old.c +++ b/video/decode/vdpau_old.c @@ -223,9 +223,6 @@ static void uninit(struct lavc_ctx *ctx) 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, @@ -246,6 +243,14 @@ static int init(struct lavc_ctx *ctx) return 0; } +static int probe(struct vd_lavc_hwdec *hwdec, struct mp_hwdec_info *info, + const char *decoder) +{ + if (!info || !info->vdpau_ctx) + return HWDEC_ERR_NO_CTX; + return 0; +} + static void fix_image(struct lavc_ctx *ctx, struct mp_image *img) { // Make it follow the convention of the "new" vdpau decoder @@ -254,12 +259,23 @@ static void fix_image(struct lavc_ctx *ctx, struct mp_image *img) img->planes[3] = (void *)(intptr_t)rndr->surface; } -const struct vd_lavc_hwdec_functions mp_vd_lavc_vdpau_old = { +const struct vd_lavc_hwdec 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 }, + .codec_pairs = (const char *[]) { + "h264", "h264_vdpau", + "wmv3", "wmv3_vdpau", + "vc1", "vc1_vdpau", + "mpegvideo", "mpegvideo_vdpau", + "mpeg1video", "mpeg1video_vdpau", + "mpeg2video", "mpegvideo_vdpau", + "mpeg2", "mpeg2_vdpau", + "mpeg4", "mpeg4_vdpau", + NULL + }, .init = init, .uninit = uninit, .allocate_image = allocate_image,