1
0
mirror of https://github.com/mpv-player/mpv synced 2025-03-25 04:38:01 +00:00

vd_lavc: rewrite how --hwdec is handled

Change it from explicit metadata about every hwaccel method to trying to
get it from libavcodec. As shown by add_all_hwdec_methods(), this is a
quite bumpy road, and a bit worse than expected.

This will probably cause a bunch of regressions. In particular I didn't
check all the strange decoder wrappers, which all cause some sort of
special cases each. You're volunteering for beta testing by using this
commit.

One interesting thing is that we completely get rid of mp_hwdec_ctx in
vd_lavc.c, and that HWDEC_* mostly goes away (some filters still use it,
and the VO hwdec interops still have a lot of code to set it up, so it's
not going away completely for now).
This commit is contained in:
wm4 2017-12-01 21:05:54 +01:00
parent 43af055a70
commit eb8957cea1
8 changed files with 389 additions and 551 deletions

View File

@ -57,6 +57,8 @@ Interface changes
This option is hereby declared as unstable and may change any time - its
old use is deprecated, and it has very little use outside of debugging
now.
- change the --hwdec option from a choice to a plain string (affects
introspection of the option/property), also affects some properties
--- mpv 0.27.0 ---
- drop previously deprecated --field-dominance option
- drop previously deprecated "osd" command

View File

@ -93,34 +93,6 @@ extern const struct m_sub_options d3d11va_conf;
extern const struct m_sub_options angle_conf;
extern const struct m_sub_options cocoa_conf;
const struct m_opt_choice_alternatives mp_hwdec_names[] = {
{"no", HWDEC_NONE},
{"auto", HWDEC_AUTO},
{"yes" , HWDEC_AUTO},
{"auto-copy", HWDEC_AUTO_COPY},
{"vdpau", HWDEC_VDPAU},
{"vdpau-copy", HWDEC_VDPAU_COPY},
{"videotoolbox",HWDEC_VIDEOTOOLBOX},
{"videotoolbox-copy",HWDEC_VIDEOTOOLBOX_COPY},
{"vaapi", HWDEC_VAAPI},
{"vaapi-copy", HWDEC_VAAPI_COPY},
{"dxva2", HWDEC_DXVA2},
{"dxva2-copy", HWDEC_DXVA2_COPY},
{"d3d11va", HWDEC_D3D11VA},
{"d3d11va-copy",HWDEC_D3D11VA_COPY},
{"rpi", HWDEC_RPI},
{"rpi-copy", HWDEC_RPI_COPY},
{"rkmpp", HWDEC_RKMPP},
{"mediacodec", HWDEC_MEDIACODEC},
{"mediacodec-copy",HWDEC_MEDIACODEC_COPY},
{"cuda", HWDEC_CUDA},
{"cuda-copy", HWDEC_CUDA_COPY},
{"nvdec", HWDEC_NVDEC},
{"nvdec-copy", HWDEC_NVDEC_COPY},
{"crystalhd", HWDEC_CRYSTALHD},
{0}
};
static const struct m_sub_options screenshot_conf = {
.opts = image_writer_opts,
.size = sizeof(struct image_writer_opts),
@ -434,7 +406,8 @@ const m_option_t mp_opts[] = {
OPT_FLAG("ad-spdif-dtshd", dtshd, 0,
.deprecation_message = "use --audio-spdif instead"),
OPT_CHOICE_C("hwdec", hwdec_api, 0, mp_hwdec_names),
OPT_STRING_VALIDATE("hwdec", hwdec_api, M_OPT_OPTIONAL_PARAM,
hwdec_validate_opt),
OPT_STRING("hwdec-codecs", hwdec_codecs, 0),
#if HAVE_VIDEOTOOLBOX_HWACCEL
OPT_IMAGEFORMAT("videotoolbox-format", videotoolbox_format, 0, .min = -1,
@ -943,7 +916,7 @@ const struct MPOpts mp_default_opts = {
.use_embedded_fonts = 1,
.screenshot_template = "mpv-shot%n",
.hwdec_api = HAVE_RPI ? HWDEC_RPI : 0,
.hwdec_api = HAVE_RPI ? "mmal" : "no",
.hwdec_codecs = "h264,vc1,wmv3,hevc,mpeg2video,vp9",
.videotoolbox_format = IMGFMT_NV12,

View File

@ -285,7 +285,7 @@ typedef struct MPOpts {
int sub_clear_on_seek;
int teletext_page;
int hwdec_api;
char *hwdec_api;
char *hwdec_codecs;
int videotoolbox_format;
int hwdec_image_format;
@ -349,4 +349,7 @@ extern const struct m_sub_options vo_sub_opts;
extern const struct m_sub_options stream_cache_conf;
extern const struct m_sub_options dvd_conf;
int hwdec_validate_opt(struct mp_log *log, const m_option_t *opt,
struct bstr name, struct bstr param);
#endif

View File

@ -2419,24 +2419,22 @@ static int mp_property_hwdec(void *ctx, struct m_property *prop,
struct MPOpts *opts = mpctx->opts;
if (action == M_PROPERTY_SET) {
int new = *(int *)arg;
char *new = *(char **)arg;
if (opts->hwdec_api == new)
if (strcmp(opts->hwdec_api, new) == 0)
return M_PROPERTY_OK;
opts->hwdec_api = new;
talloc_free(opts->hwdec_api);
opts->hwdec_api = talloc_strdup(NULL, new);
if (!vd)
return M_PROPERTY_OK;
int current = -2;
video_vd_control(vd, VDCTRL_GET_HWDEC, &current);
if (current != opts->hwdec_api) {
video_vd_control(vd, VDCTRL_REINIT, NULL);
double last_pts = mpctx->last_vo_pts;
if (last_pts != MP_NOPTS_VALUE)
queue_seek(mpctx, MPSEEK_ABSOLUTE, last_pts, MPSEEK_EXACT, 0);
}
video_vd_control(vd, VDCTRL_REINIT, NULL);
double last_pts = mpctx->last_vo_pts;
if (last_pts != MP_NOPTS_VALUE)
queue_seek(mpctx, MPSEEK_ABSOLUTE, last_pts, MPSEEK_EXACT, 0);
return M_PROPERTY_OK;
}
return mp_property_generic_option(mpctx, prop, action, arg);
@ -2452,20 +2450,11 @@ static int mp_property_hwdec_current(void *ctx, struct m_property *prop,
if (!vd)
return M_PROPERTY_UNAVAILABLE;
switch (action) {
case M_PROPERTY_GET_TYPE: {
// Abuse another hwdec option to resolve the value names
struct m_property dummy = {.name = "hwdec"};
return mp_property_generic_option(mpctx, &dummy, action, arg);
}
case M_PROPERTY_GET: {
int current = HWDEC_NONE;
video_vd_control(vd, VDCTRL_GET_HWDEC, &current);
*(int *)arg = current;
return M_PROPERTY_OK;
}
}
return M_PROPERTY_NOT_IMPLEMENTED;
char *current = NULL;
video_vd_control(vd, VDCTRL_GET_HWDEC, &current);
if (!current)
current = "no";
return m_property_strdup_ro(action, arg, current);
}
static int mp_property_hwdec_interop(void *ctx, struct m_property *prop,

View File

@ -24,12 +24,28 @@
struct mpv_global;
struct hwdec_info {
char name[64];
char method_name[16]; // non-unique name describing the hwdec method
const AVCodec *codec; // implemented by this codec
enum AVHWDeviceType lavc_device; // if not NONE, get a hwdevice
bool copying; // if true, outputs sw frames, or copy to sw ourselves
enum AVPixelFormat pix_fmt; // if not NONE, select in get_format
bool use_hw_frames; // set AVCodecContext.hw_frames_ctx
bool use_hw_device; // set AVCodecContext.hw_device_ctx
// for internal sorting
int auto_pos;
int rank;
};
typedef struct lavc_ctx {
struct mp_log *log;
struct MPOpts *opts;
AVCodecContext *avctx;
AVFrame *pic;
struct vd_lavc_hwdec *hwdec;
bool use_hwdec;
struct hwdec_info hwdec; // valid only if use_hwdec==true
AVRational codec_timebase;
enum AVDiscard skip_frame;
bool flushing;
@ -54,12 +70,8 @@ typedef struct lavc_ctx {
// From VO
struct mp_hwdec_devices *hwdec_devs;
// For free use by hwdec implementation
void *hwdec_priv;
// Set by generic hwaccels.
struct mp_hwdec_ctx *hwdec_dev;
bool owns_hwdec_dev;
// Wrapped AVHWDeviceContext* used for decoding.
AVBufferRef *hwdec_dev;
bool hwdec_request_reinit;
int hwdec_fail_count;
@ -75,43 +87,4 @@ typedef struct lavc_ctx {
int dr_imgfmt, dr_w, dr_h, dr_stride_align;
} vd_ffmpeg_ctx;
struct vd_lavc_hwdec {
enum hwdec_type type;
// If non-0, get this hwdec type from the VO (for the AVHWDeviceContext).
enum hwdec_type interop_type;
// If true, create a AVHWDeviceContext with default parameters. In this
// case, create_standalone_dev_type is set to a valid value.
bool create_standalone_dev;
enum AVHWDeviceType create_standalone_dev_type;
// If not-0: the IMGFMT_ format that should be accepted in the libavcodec
// get_format callback.
int image_format;
// Always returns a non-hwaccel image format.
bool copying;
// Setting this will queue the given number of frames before returning them
// to the renderer. This can increase efficiency by not blocking on the
// hardware pipeline by reading back immediately after decoding.
int delay_queue;
int (*probe)(struct lavc_ctx *ctx, struct vd_lavc_hwdec *hwdec,
const char *codec);
int (*init)(struct lavc_ctx *ctx);
int (*init_decoder)(struct lavc_ctx *ctx);
void (*uninit)(struct lavc_ctx *ctx);
// Suffix for libavcodec decoder. If non-NULL, the codec is overridden
// with hwdec_find_decoder.
// Intuitively, this will force the corresponding wrapper decoder.
const char *lavc_suffix;
// Generic hwaccels set AVCodecContext.hw_frames_ctx in get_format().
bool generic_hwaccel;
// If set, AVCodecContext.hw_frames_ctx will be initialized in get_format,
// and pixfmt_map must be non-NULL.
bool set_hwframes;
};
enum {
HWDEC_ERR_NO_CTX = -2,
HWDEC_ERR_NO_CODEC = -3,
HWDEC_ERR_EMULATED = -4, // probing successful, but emulated API detected
};
#endif

View File

@ -56,8 +56,7 @@
#include "options/m_option.h"
static void init_avctx(struct dec_video *vd, const char *decoder,
struct vd_lavc_hwdec *hwdec);
static void init_avctx(struct dec_video *vd);
static void uninit_avctx(struct dec_video *vd);
static int get_buffer2_direct(AVCodecContext *avctx, AVFrame *pic, int flags);
@ -123,216 +122,185 @@ const struct m_sub_options vd_lavc_conf = {
},
};
#if HAVE_RPI
static const struct vd_lavc_hwdec mp_vd_lavc_rpi = {
.type = HWDEC_RPI,
.lavc_suffix = "_mmal",
.image_format = IMGFMT_MMAL,
};
static const struct vd_lavc_hwdec mp_vd_lavc_rpi_copy = {
.type = HWDEC_RPI_COPY,
.lavc_suffix = "_mmal",
.copying = true,
};
#endif
static const struct vd_lavc_hwdec mp_vd_lavc_rkmpp = {
.type = HWDEC_RKMPP,
.lavc_suffix = "_rkmpp",
.image_format = IMGFMT_DRMPRIME,
// Things not included in this list will be tried last, in random order.
static const char *const hwdec_autoprobe_order[] = {
"d3d11va",
"dxva2",
"dxva2-copy",
"d3d11va-copy",
"nvdec",
"nvdec-copy",
"vdpau",
"vdpau-copy",
"vaapi",
"vaapi-copy",
0
};
#if HAVE_CUDA_HWACCEL
static const struct vd_lavc_hwdec mp_vd_lavc_nvdec = {
.type = HWDEC_NVDEC,
.interop_type = HWDEC_CUDA,
.image_format = IMGFMT_CUDA,
.generic_hwaccel = true,
.set_hwframes = true,
};
static const struct vd_lavc_hwdec mp_vd_lavc_nvdec_copy = {
.type = HWDEC_NVDEC_COPY,
.create_standalone_dev = true,
.create_standalone_dev_type = AV_HWDEVICE_TYPE_CUDA,
.generic_hwaccel = true,
.set_hwframes = true,
.copying = true,
};
static const struct vd_lavc_hwdec mp_vd_lavc_cuda = {
.type = HWDEC_CUDA,
.image_format = IMGFMT_CUDA,
.lavc_suffix = "_cuvid",
.generic_hwaccel = true,
};
static const struct vd_lavc_hwdec mp_vd_lavc_cuda_copy = {
.type = HWDEC_CUDA_COPY,
.lavc_suffix = "_cuvid",
.copying = true,
};
#endif
static const struct vd_lavc_hwdec mp_vd_lavc_crystalhd = {
.type = HWDEC_CRYSTALHD,
.lavc_suffix = "_crystalhd",
.copying = true,
};
#if HAVE_VAAPI
static const struct vd_lavc_hwdec mp_vd_lavc_vaapi = {
.type = HWDEC_VAAPI,
.image_format = IMGFMT_VAAPI,
.generic_hwaccel = true,
.set_hwframes = true,
};
#include "video/vaapi.h"
static const struct vd_lavc_hwdec mp_vd_lavc_vaapi_copy = {
.type = HWDEC_VAAPI_COPY,
.copying = true,
.image_format = IMGFMT_VAAPI,
.generic_hwaccel = true,
.set_hwframes = true,
.create_standalone_dev = true,
.create_standalone_dev_type = AV_HWDEVICE_TYPE_VAAPI,
};
#endif
#if HAVE_VDPAU
static const struct vd_lavc_hwdec mp_vd_lavc_vdpau = {
.type = HWDEC_VDPAU,
.image_format = IMGFMT_VDPAU,
.generic_hwaccel = true,
.set_hwframes = true,
};
#include "video/vdpau.h"
static const struct vd_lavc_hwdec mp_vd_lavc_vdpau_copy = {
.type = HWDEC_VDPAU_COPY,
.copying = true,
.image_format = IMGFMT_VDPAU,
.generic_hwaccel = true,
.set_hwframes = true,
.create_standalone_dev = true,
.create_standalone_dev_type = AV_HWDEVICE_TYPE_VDPAU,
};
#endif
#if HAVE_VIDEOTOOLBOX_HWACCEL
static const struct vd_lavc_hwdec mp_vd_lavc_videotoolbox = {
.type = HWDEC_VIDEOTOOLBOX,
.image_format = IMGFMT_VIDEOTOOLBOX,
.generic_hwaccel = true,
.set_hwframes = true,
};
static const struct vd_lavc_hwdec mp_vd_lavc_videotoolbox_copy = {
.type = HWDEC_VIDEOTOOLBOX_COPY,
.copying = true,
.image_format = IMGFMT_VIDEOTOOLBOX,
.generic_hwaccel = true,
.create_standalone_dev = true,
.create_standalone_dev_type = AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
.set_hwframes = true,
.delay_queue = HWDEC_DELAY_QUEUE_COUNT,
};
#endif
#if HAVE_D3D_HWACCEL
static const struct vd_lavc_hwdec mp_vd_lavc_d3d11va = {
.type = HWDEC_D3D11VA,
.image_format = IMGFMT_D3D11VA,
.generic_hwaccel = true,
.set_hwframes = true,
};
static const struct vd_lavc_hwdec mp_vd_lavc_d3d11va_copy = {
.type = HWDEC_D3D11VA_COPY,
.copying = true,
.image_format = IMGFMT_D3D11VA,
.generic_hwaccel = true,
.create_standalone_dev = true,
.create_standalone_dev_type = AV_HWDEVICE_TYPE_D3D11VA,
.set_hwframes = true,
.delay_queue = HWDEC_DELAY_QUEUE_COUNT,
};
#endif
#if HAVE_D3D9_HWACCEL
static const struct vd_lavc_hwdec mp_vd_lavc_dxva2 = {
.type = HWDEC_DXVA2,
.image_format = IMGFMT_DXVA2,
.generic_hwaccel = true,
.set_hwframes = true,
};
static const struct vd_lavc_hwdec mp_vd_lavc_dxva2_copy = {
.type = HWDEC_DXVA2_COPY,
.copying = true,
.image_format = IMGFMT_DXVA2,
.generic_hwaccel = true,
.create_standalone_dev = true,
.create_standalone_dev_type = AV_HWDEVICE_TYPE_DXVA2,
.set_hwframes = true,
.delay_queue = HWDEC_DELAY_QUEUE_COUNT,
};
#endif
#if HAVE_ANDROID
static const struct vd_lavc_hwdec mp_vd_lavc_mediacodec_copy = {
.type = HWDEC_MEDIACODEC_COPY,
.lavc_suffix = "_mediacodec",
.copying = true,
};
#endif
static const struct vd_lavc_hwdec *const hwdec_list[] = {
#if HAVE_D3D_HWACCEL
&mp_vd_lavc_d3d11va,
#if HAVE_D3D9_HWACCEL
&mp_vd_lavc_dxva2,
&mp_vd_lavc_dxva2_copy,
#endif
&mp_vd_lavc_d3d11va_copy,
#endif
#if HAVE_RPI
&mp_vd_lavc_rpi,
&mp_vd_lavc_rpi_copy,
#endif
#if HAVE_CUDA_HWACCEL
&mp_vd_lavc_nvdec,
&mp_vd_lavc_nvdec_copy,
#endif
#if HAVE_VDPAU
&mp_vd_lavc_vdpau,
&mp_vd_lavc_vdpau_copy,
#endif
#if HAVE_VIDEOTOOLBOX_HWACCEL
&mp_vd_lavc_videotoolbox,
&mp_vd_lavc_videotoolbox_copy,
#endif
#if HAVE_VAAPI
&mp_vd_lavc_vaapi,
&mp_vd_lavc_vaapi_copy,
#endif
#if HAVE_ANDROID
&mp_vd_lavc_mediacodec_copy,
#endif
#if HAVE_CUDA_HWACCEL
&mp_vd_lavc_cuda,
&mp_vd_lavc_cuda_copy,
#endif
&mp_vd_lavc_crystalhd,
&mp_vd_lavc_rkmpp,
static const char *const hwdec_wrappers[] = {
"mmal",
"mediacodec",
"crystalhd",
"v4l2m2m",
"cuvid",
NULL
};
static struct vd_lavc_hwdec *find_hwcodec(enum hwdec_type api)
static int hwdec_compare(const void *p1, const void *p2)
{
for (int n = 0; hwdec_list[n]; n++) {
if (hwdec_list[n]->type == api)
return (struct vd_lavc_hwdec *)hwdec_list[n];
struct hwdec_info *h1 = (void *)p1;
struct hwdec_info *h2 = (void *)p2;
if (h1 == h2)
return 0;
if (h1->auto_pos > h2->auto_pos)
return 1;
else if (h1->auto_pos < h2->auto_pos)
return -1;
return h1->rank < h2->rank ? 1 :-1;
}
static bool test_decoder_suffix(const char *name, const char *suffix)
{
bstr bname = bstr0(name);
return bstr_eatend0(&bname, suffix) && bstr_eatend0(&bname, "_");
}
static void add_all_hwdec_methods(struct hwdec_info **infos, int *num_infos)
{
AVCodec *codec = NULL;
while (1) {
codec = av_codec_next(codec);
if (!codec)
break;
struct hwdec_info info_template = {
.pix_fmt = AV_PIX_FMT_NONE,
.codec = codec,
};
const char *wrapper = NULL;
for (int n = 0; hwdec_wrappers[n]; n++) {
if (test_decoder_suffix(codec->name, hwdec_wrappers[n])) {
wrapper = hwdec_wrappers[n];
// Different lavc/mpv names.
if (strcmp(wrapper, "mmal") == 0)
wrapper = "rpi";
break;
}
}
// A decoder can provide multiple methods. In particular, hwaccels
// provide various methods (e.g. native h264 with vaapi & d3d11), but
// even wrapper decoders could provide multiple methods.
bool found_any = false;
for (int n = 0; ; n++) {
const AVCodecHWConfig *cfg = avcodec_get_hw_config(codec, n);
if (!cfg)
break;
if ((cfg->methods & AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX) ||
(cfg->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX))
{
struct hwdec_info info = info_template;
info.lavc_device = cfg->device_type;
info.pix_fmt = cfg->pix_fmt;
const char *name = av_hwdevice_get_type_name(cfg->device_type);
assert(name); // API violation by libavcodec
// nvdec hwaccels and the cuvid full decoder clash with their
// naming, so fix it here; we also prefer nvdec for the hwaccel.
if (strcmp(name, "cuda") == 0 && !wrapper)
name = "nvdec";
snprintf(info.method_name, sizeof(info.method_name), "%s", name);
snprintf(info.name, sizeof(info.name), "%s-%s",
codec->name, info.method_name);
// Usually we want to prefer using hw_frames_ctx for true
// hwaccels only, but we actually don't have any way to detect
// those, so always use hw_frames_ctx if offered.
if (cfg->methods & AV_CODEC_HW_CONFIG_METHOD_HW_FRAMES_CTX) {
info.use_hw_frames = true;
} else {
info.use_hw_device = true;
}
// Direct variant.
MP_TARRAY_APPEND(NULL, *infos, *num_infos, info);
// Copy variant.
info.copying = true;
if (cfg->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX) {
info.use_hw_frames = false;
info.use_hw_device = true;
}
mp_snprintf_cat(info.method_name, sizeof(info.method_name),
"-copy");
snprintf(info.name, sizeof(info.name), "%s-%s",
codec->name, info.method_name);
MP_TARRAY_APPEND(NULL, *infos, *num_infos, info);
found_any = true;
} else if (cfg->methods & AV_CODEC_HW_CONFIG_METHOD_INTERNAL) {
struct hwdec_info info = info_template;
info.pix_fmt = cfg->pix_fmt;
const char *name = wrapper;
if (!name)
name = av_get_pix_fmt_name(info.pix_fmt);
assert(name); // API violation by libavcodec
snprintf(info.method_name, sizeof(info.method_name), "%s", name);
snprintf(info.name, sizeof(info.name), "%s-%s",
codec->name, info.method_name);
// Direct variant.
MP_TARRAY_APPEND(NULL, *infos, *num_infos, info);
// Copy variant.
mp_snprintf_cat(info.method_name, sizeof(info.method_name),
"-copy");
snprintf(info.name, sizeof(info.name), "%s-%s",
codec->name, info.method_name);
MP_TARRAY_APPEND(NULL, *infos, *num_infos, info);
found_any = true;
}
}
if (!found_any && wrapper) {
// We _know_ there's something supported here, usually outputting
// sw surfaces. E.g. mediacodec (before hw_device_ctx support).
struct hwdec_info info = info_template;
info.copying = true; // probably
snprintf(info.method_name, sizeof(info.method_name), "%s", wrapper);
// (Including the codec name for wrappers looks pretty dumb, but
// better not have them clash with hwaccels and others.)
snprintf(info.name, sizeof(info.name), "%s-%s",
codec->name, info.method_name);
MP_TARRAY_APPEND(NULL, *infos, *num_infos, info);
}
}
return NULL;
for (int n = 0; n < *num_infos; n++) {
struct hwdec_info *hwdec = &(*infos)[n];
hwdec->rank = n;
hwdec->auto_pos = MP_ARRAY_SIZE(hwdec_autoprobe_order);
for (int x = 0; hwdec_autoprobe_order[x]; x++) {
if (strcmp(hwdec_autoprobe_order[x], hwdec->method_name) == 0)
hwdec->auto_pos = x;
}
}
qsort(*infos, *num_infos, sizeof(struct hwdec_info), hwdec_compare);
}
static bool hwdec_codec_allowed(struct dec_video *vd, const char *codec)
@ -347,141 +315,121 @@ static bool hwdec_codec_allowed(struct dec_video *vd, const char *codec)
return false;
}
// This is intended to return the name of a decoder for a given wrapper API.
// Decoder wrappers are usually added to libavcodec with a specific suffix.
// For example the mmal h264 decoder is named h264_mmal.
// This API would e.g. return h264_mmal for
// hwdec_find_decoder("h264", "_mmal").
// Just concatenating the two names will not always work due to inconsistencies
// (e.g. "mpeg2video" vs. "mpeg2").
static const char *hwdec_find_decoder(const char *codec, const char *suffix)
static AVBufferRef *hwdec_create_dev(struct dec_video *vd,
struct hwdec_info *hwdec,
bool autoprobe)
{
enum AVCodecID codec_id = mp_codec_to_av_codec_id(codec);
if (codec_id == AV_CODEC_ID_NONE)
return NULL;
AVCodec *cur = NULL;
for (;;) {
cur = av_codec_next(cur);
if (!cur)
break;
if (cur->id == codec_id && av_codec_is_decoder(cur) &&
bstr_endswith0(bstr0(cur->name), suffix))
return cur->name;
}
return NULL;
}
assert(hwdec->lavc_device);
// Parallel to hwdec_find_decoder(): return whether a hwdec can use the given
// decoder. This can't be answered accurately; it works for wrapper decoders
// only (like mmal), and for real hwaccels this will always return false.
static bool hwdec_is_wrapper(struct vd_lavc_hwdec *hwdec, const char *decoder)
{
if (!hwdec->lavc_suffix)
return false;
return bstr_endswith0(bstr0(decoder), hwdec->lavc_suffix);
}
static void standalone_dev_destroy(struct mp_hwdec_ctx *ctx)
{
av_buffer_unref(&ctx->av_device_ref);
talloc_free(ctx);
}
static struct mp_hwdec_ctx *hwdec_create_dev(struct dec_video *vd,
struct vd_lavc_hwdec *hwdec,
bool autoprobe)
{
if (hwdec->create_standalone_dev) {
struct mp_hwdec_ctx *ctx = talloc_ptrtype(NULL, ctx);
*ctx = (struct mp_hwdec_ctx) {
.type = hwdec->type,
.ctx = NULL,
.destroy = standalone_dev_destroy,
};
if (hwdec->copying) {
const struct hwcontext_fns *fns =
hwdec_get_hwcontext_fns(hwdec->create_standalone_dev_type);
hwdec_get_hwcontext_fns(hwdec->lavc_device);
if (fns && fns->create_dev) {
struct hwcontext_create_dev_params params = {
.probing = autoprobe,
};
ctx->av_device_ref = fns->create_dev(vd->global, vd->log, &params);
if (!ctx->av_device_ref) {
standalone_dev_destroy(ctx);
ctx = NULL;
}
return fns->create_dev(vd->global, vd->log, &params);
} else {
if (av_hwdevice_ctx_create(&ctx->av_device_ref,
hwdec->create_standalone_dev_type, NULL, NULL, 0) < 0)
{
standalone_dev_destroy(ctx);
ctx = NULL;
}
AVBufferRef* ref = NULL;
av_hwdevice_ctx_create(&ref, hwdec->lavc_device, NULL, NULL, 0);
return ref;
}
return ctx;
}
if (vd->hwdec_devs) {
int type = hwdec->interop_type ? hwdec->interop_type : hwdec->type;
} else if (vd->hwdec_devs) {
hwdec_devices_request_all(vd->hwdec_devs);
return hwdec_devices_get(vd->hwdec_devs, type);
return hwdec_devices_get_lavc(vd->hwdec_devs, hwdec->lavc_device);
}
return NULL;
}
static int hwdec_probe(struct dec_video *vd, struct vd_lavc_hwdec *hwdec,
const char *codec, bool autoprobe)
// Select if and which hwdec to use. Also makes sure to get the decode device.
static void select_and_set_hwdec(struct dec_video *vd)
{
vd_ffmpeg_ctx *ctx = vd->priv;
int r = 0;
if (hwdec->probe)
r = hwdec->probe(ctx, hwdec, codec);
if (hwdec->generic_hwaccel) {
assert(!hwdec->probe && !hwdec->init && !hwdec->init_decoder &&
!hwdec->uninit);
struct mp_hwdec_ctx *dev = hwdec_create_dev(vd, hwdec, autoprobe);
if (!dev)
return hwdec->copying ? -1 : HWDEC_ERR_NO_CTX;
if (dev->emulated)
r = HWDEC_ERR_EMULATED;
bool owns_hwdec_dev = !!hwdec->create_standalone_dev;
if (owns_hwdec_dev && dev->destroy)
dev->destroy(dev);
const char *codec = vd->codec->codec;
bstr opt = bstr0(vd->opts->hwdec_api);
bool hwdec_requested = !bstr_equals0(opt, "no");
bool hwdec_auto_all = bstr_equals0(opt, "auto") ||
bstr_equals0(opt, "yes") ||
bstr_equals0(opt, "");
bool hwdec_auto_copy = bstr_equals0(opt, "auto-copy");
bool hwdec_auto = hwdec_auto_all || hwdec_auto_copy;
if (hwdec_codec_allowed(vd, codec) && hwdec_requested) {
struct hwdec_info *hwdecs = NULL;
int num_hwdecs = 0;
add_all_hwdec_methods(&hwdecs, &num_hwdecs);
for (int n = 0; n < num_hwdecs; n++) {
struct hwdec_info *hwdec = &hwdecs[n];
const char *hw_codec = mp_codec_from_av_codec_id(hwdec->codec->id);
if (!hw_codec || strcmp(hw_codec, codec) != 0)
continue;
if (!hwdec_auto && !bstr_equals0(opt, hwdec->method_name))
continue;
MP_VERBOSE(vd, "Looking at hwdec %s...\n", hwdec->name);
if (hwdec_auto_copy && !hwdec->copying) {
MP_VERBOSE(vd, "Not using this for auto-copy.\n");
continue;
}
if (hwdec->lavc_device) {
ctx->hwdec_dev = hwdec_create_dev(vd, hwdec, hwdec_auto);
if (!ctx->hwdec_dev) {
MP_VERBOSE(vd, "Could not create device.\n");
continue;
}
}
ctx->use_hwdec = true;
ctx->hwdec = *hwdec;
break;
}
talloc_free(hwdecs);
} else {
MP_VERBOSE(vd, "Not trying to use hardware decoding: codec %s is not "
"on whitelist, or does not support hardware acceleration.\n",
codec);
}
if (r >= 0) {
if (hwdec->lavc_suffix && !hwdec_find_decoder(codec, hwdec->lavc_suffix))
return HWDEC_ERR_NO_CODEC;
if (ctx->use_hwdec) {
MP_VERBOSE(vd, "Trying hardware decoding via %s.\n", ctx->hwdec.name);
if (strcmp(ctx->decoder, ctx->hwdec.codec->name) != 0)
MP_VERBOSE(vd, "Using underlying hw-decoder '%s'\n",
ctx->hwdec.codec->name);
} else {
MP_VERBOSE(vd, "Using software decoding.\n");
}
return r;
}
static struct vd_lavc_hwdec *probe_hwdec(struct dec_video *vd, bool autoprobe,
enum hwdec_type api,
const char *codec)
int hwdec_validate_opt(struct mp_log *log, const m_option_t *opt,
struct bstr name, struct bstr param)
{
MP_VERBOSE(vd, "Probing '%s'...\n", m_opt_choice_str(mp_hwdec_names, api));
struct vd_lavc_hwdec *hwdec = find_hwcodec(api);
if (!hwdec) {
int level = autoprobe ? MSGL_V : MSGL_WARN;
MP_MSG(vd, level, "Requested hardware decoder not compiled.\n");
return NULL;
if (bstr_equals0(param, "help")) {
struct hwdec_info *hwdecs = NULL;
int num_hwdecs = 0;
add_all_hwdec_methods(&hwdecs, &num_hwdecs);
mp_info(log, "Valid values:\n");
for (int n = 0; n < num_hwdecs; n++) {
struct hwdec_info *hwdec = &hwdecs[n];
mp_info(log, " %s (%s)\n", hwdec->method_name, hwdec->name);
}
talloc_free(hwdecs);
return M_OPT_EXIT;
}
int r = hwdec_probe(vd, hwdec, codec, autoprobe);
if (r == HWDEC_ERR_EMULATED) {
if (autoprobe)
return NULL;
// User requested this explicitly.
MP_WARN(vd, "Using emulated hardware decoding API.\n");
r = 0;
}
if (r >= 0) {
return hwdec;
} else if (r == HWDEC_ERR_NO_CODEC) {
MP_VERBOSE(vd, "Hardware decoder for '%s' with the given API not found "
"in libavcodec.\n", codec);
} else if (r == HWDEC_ERR_NO_CTX && !autoprobe) {
MP_WARN(vd, "VO does not support requested hardware decoder, or "
"loading it failed.\n");
}
return NULL;
return 0;
}
static void uninit(struct dec_video *vd)
@ -501,72 +449,20 @@ static void force_fallback(struct dec_video *vd)
uninit_avctx(vd);
int lev = ctx->hwdec_notified ? MSGL_WARN : MSGL_V;
mp_msg(vd->log, lev, "Falling back to software decoding.\n");
init_avctx(vd, ctx->decoder, NULL);
init_avctx(vd);
}
static void reinit(struct dec_video *vd)
{
vd_ffmpeg_ctx *ctx = vd->priv;
const char *decoder = ctx->decoder;
const char *codec = vd->codec->codec;
uninit_avctx(vd);
struct vd_lavc_hwdec *hwdec = NULL;
select_and_set_hwdec(vd);
if (hwdec_codec_allowed(vd, codec)) {
int api = vd->opts->hwdec_api;
if (HWDEC_IS_AUTO(api)) {
// If a specific decoder is forced, we should try a hwdec method
// that works with it, instead of simply failing later at runtime.
// This is good for avoiding trying "normal" hwaccels on wrapper
// decoders (like vaapi on a mmal decoder). Since libavcodec doesn't
// tell us which decoder supports which hwaccel methods without
// actually running it, do it by detecting such wrapper decoders.
// On the other hand, e.g. "--hwdec=rpi" should always force the
// wrapper decoder, so be careful not to break this case.
bool might_be_wrapper = false;
for (int n = 0; hwdec_list[n]; n++) {
struct vd_lavc_hwdec *other = (void *)hwdec_list[n];
if (hwdec_is_wrapper(other, decoder))
might_be_wrapper = true;
}
for (int n = 0; hwdec_list[n]; n++) {
hwdec = probe_hwdec(vd, true, hwdec_list[n]->type, codec);
if (hwdec) {
if (might_be_wrapper && !hwdec_is_wrapper(hwdec, decoder)) {
MP_VERBOSE(vd, "This hwaccel is not compatible.\n");
continue;
}
if (api == HWDEC_AUTO_COPY && !hwdec->copying) {
MP_VERBOSE(vd, "Not using this for auto-copy mode.\n");
continue;
}
break;
}
}
} else if (api != HWDEC_NONE) {
hwdec = probe_hwdec(vd, false, api, codec);
}
} else {
MP_VERBOSE(vd, "Not trying to use hardware decoding: codec %s is not "
"on whitelist, or does not support hardware acceleration.\n",
codec);
}
if (hwdec) {
const char *orig_decoder = decoder;
if (hwdec->lavc_suffix)
decoder = hwdec_find_decoder(codec, hwdec->lavc_suffix);
MP_VERBOSE(vd, "Trying hardware decoding.\n");
if (strcmp(orig_decoder, decoder) != 0)
MP_VERBOSE(vd, "Using underlying hw-decoder '%s'\n", decoder);
} else {
MP_VERBOSE(vd, "Using software decoding.\n");
}
init_avctx(vd, decoder, hwdec);
if (!ctx->avctx && hwdec)
bool use_hwdec = ctx->use_hwdec;
init_avctx(vd);
if (!ctx->avctx && use_hwdec)
force_fallback(vd);
}
@ -592,8 +488,7 @@ static int init(struct dec_video *vd, const char *decoder)
return 1;
}
static void init_avctx(struct dec_video *vd, const char *decoder,
struct vd_lavc_hwdec *hwdec)
static void init_avctx(struct dec_video *vd)
{
vd_ffmpeg_ctx *ctx = vd->priv;
struct vd_lavc_params *lavc_param = vd->opts->vd_lavc_params;
@ -601,7 +496,13 @@ static void init_avctx(struct dec_video *vd, const char *decoder,
assert(!ctx->avctx);
AVCodec *lavc_codec = avcodec_find_decoder_by_name(decoder);
const AVCodec *lavc_codec = NULL;
if (ctx->use_hwdec) {
lavc_codec = ctx->hwdec.codec;
} else {
lavc_codec = avcodec_find_decoder_by_name(ctx->decoder);
}
if (!lavc_codec)
return;
@ -611,10 +512,9 @@ static void init_avctx(struct dec_video *vd, const char *decoder,
ctx->codec_timebase = mp_get_codec_timebase(vd->codec);
// This decoder does not read pkt_timebase correctly yet.
if (strstr(decoder, "_mmal"))
if (strstr(lavc_codec->name, "_mmal"))
ctx->codec_timebase = (AVRational){1, 1000000};
ctx->hwdec = hwdec;
ctx->hwdec_failed = false;
ctx->hwdec_request_reinit = false;
ctx->avctx = avcodec_alloc_context3(lavc_codec);
@ -632,31 +532,36 @@ static void init_avctx(struct dec_video *vd, const char *decoder,
if (!ctx->pic)
goto error;
if (ctx->hwdec) {
if (ctx->use_hwdec) {
avctx->opaque = vd;
avctx->thread_count = 1;
avctx->hwaccel_flags |= AV_HWACCEL_FLAG_IGNORE_LEVEL;
if (!lavc_param->check_hw_profile)
avctx->hwaccel_flags |= AV_HWACCEL_FLAG_ALLOW_PROFILE_MISMATCH;
if (ctx->hwdec->image_format)
avctx->get_format = get_format_hwdec;
if (ctx->hwdec->generic_hwaccel) {
ctx->hwdec_dev = hwdec_create_dev(vd, ctx->hwdec, false);
if (ctx->hwdec.use_hw_device) {
if (ctx->hwdec_dev)
avctx->hw_device_ctx = av_buffer_ref(ctx->hwdec_dev);
if (!avctx->hw_device_ctx)
goto error;
}
if (ctx->hwdec.use_hw_frames) {
if (!ctx->hwdec_dev)
goto error;
ctx->owns_hwdec_dev = ctx->hwdec->create_standalone_dev;
if (ctx->hwdec_dev->restore_device)
ctx->hwdec_dev->restore_device(ctx->hwdec_dev);
if (!ctx->hwdec->set_hwframes)
avctx->hw_device_ctx = av_buffer_ref(ctx->hwdec_dev->av_device_ref);
}
ctx->max_delay_queue = ctx->hwdec->delay_queue;
if (ctx->hwdec.pix_fmt != AV_PIX_FMT_NONE)
avctx->get_format = get_format_hwdec;
// Some APIs benefit from this, for others it's additional bloat.
if (ctx->hwdec.copying)
ctx->max_delay_queue = HWDEC_DELAY_QUEUE_COUNT;
ctx->hw_probing = true;
} else {
mp_set_avcodec_threads(vd->log, avctx, lavc_param->threads);
}
if (!ctx->hwdec && vd->vo && lavc_param->dr) {
if (!ctx->use_hwdec && vd->vo && lavc_param->dr) {
avctx->opaque = vd;
avctx->get_buffer2 = get_buffer2_direct;
avctx->thread_safe_callbacks = 1;
@ -730,43 +635,33 @@ static void uninit_avctx(struct dec_video *vd)
av_frame_free(&ctx->pic);
av_buffer_unref(&ctx->cached_hw_frames_ctx);
if (ctx->hwdec && ctx->hwdec->uninit)
ctx->hwdec->uninit(ctx);
ctx->hwdec = NULL;
assert(ctx->hwdec_priv == NULL);
avcodec_free_context(&ctx->avctx);
if (ctx->hwdec_dev && ctx->owns_hwdec_dev && ctx->hwdec_dev->destroy)
ctx->hwdec_dev->destroy(ctx->hwdec_dev);
ctx->hwdec_dev = NULL;
ctx->owns_hwdec_dev = false;
av_buffer_unref(&ctx->hwdec_dev);
ctx->hwdec_failed = false;
ctx->hwdec_fail_count = 0;
ctx->max_delay_queue = 0;
ctx->hw_probing = false;
ctx->hwdec = (struct hwdec_info){0};
ctx->use_hwdec = false;
}
static int init_generic_hwaccel(struct dec_video *vd, enum AVPixelFormat hw_fmt)
{
struct lavc_ctx *ctx = vd->priv;
struct vd_lavc_hwdec *hwdec = ctx->hwdec;
AVBufferRef *new_frames_ctx = NULL;
if (!ctx->hwdec_dev)
goto error;
if (!hwdec->set_hwframes)
if (!ctx->hwdec.use_hw_frames)
return 0;
if (!ctx->hwdec_dev->av_device_ref) {
if (!ctx->hwdec_dev) {
MP_ERR(ctx, "Missing device context.\n");
goto error;
}
if (avcodec_get_hw_frames_parameters(ctx->avctx,
ctx->hwdec_dev->av_device_ref, hw_fmt, &new_frames_ctx) < 0)
ctx->hwdec_dev, hw_fmt, &new_frames_ctx) < 0)
{
MP_VERBOSE(ctx, "Hardware decoding of this stream is unsupported?\n");
goto error;
@ -774,30 +669,11 @@ static int init_generic_hwaccel(struct dec_video *vd, enum AVPixelFormat hw_fmt)
AVHWFramesContext *new_fctx = (void *)new_frames_ctx->data;
if (hwdec->image_format == IMGFMT_VIDEOTOOLBOX)
if (ctx->hwdec.pix_fmt == AV_PIX_FMT_VIDEOTOOLBOX)
new_fctx->sw_format = imgfmt2pixfmt(vd->opts->videotoolbox_format);
if (vd->opts->hwdec_image_format)
new_fctx->sw_format = imgfmt2pixfmt(vd->opts->hwdec_image_format);
// The video output might not support all formats.
// Note that supported_formats==NULL means any are accepted.
int *render_formats = ctx->hwdec_dev->supported_formats;
if (render_formats) {
int mp_format = pixfmt2imgfmt(new_fctx->sw_format);
bool found = false;
for (int n = 0; render_formats[n]; n++) {
if (render_formats[n] == mp_format) {
found = true;
break;
}
}
if (!found) {
MP_WARN(ctx, "Surface format %s not supported for direct rendering.\n",
mp_imgfmt_to_name(mp_format));
goto error;
}
}
// 1 surface is already included by libavcodec. The field is 0 if the
// hwaccel supports dynamic surface allocation.
if (new_fctx->initial_pool_size)
@ -859,22 +735,16 @@ static enum AVPixelFormat get_format_hwdec(struct AVCodecContext *avctx,
MP_VERBOSE(vd, "Codec profile: %s (0x%x)\n", profile ? profile : "unknown",
avctx->profile);
assert(ctx->hwdec);
assert(ctx->use_hwdec);
ctx->hwdec_request_reinit |= ctx->hwdec_failed;
ctx->hwdec_failed = false;
enum AVPixelFormat select = AV_PIX_FMT_NONE;
for (int i = 0; fmt[i] != AV_PIX_FMT_NONE; i++) {
if (ctx->hwdec->image_format == pixfmt2imgfmt(fmt[i])) {
if (ctx->hwdec->generic_hwaccel) {
if (init_generic_hwaccel(vd, fmt[i]) < 0)
break;
} else {
ctx->hwdec_request_reinit = false;
if (ctx->hwdec->init_decoder && ctx->hwdec->init_decoder(ctx) < 0)
break;
}
if (ctx->hwdec.pix_fmt == fmt[i]) {
if (init_generic_hwaccel(vd, fmt[i]) < 0)
break;
select = fmt[i];
break;
}
@ -1011,7 +881,7 @@ static void handle_err(struct dec_video *vd)
MP_WARN(vd, "Error while decoding frame!\n");
if (ctx->hwdec) {
if (ctx->use_hwdec) {
ctx->hwdec_fail_count += 1;
if (ctx->hwdec_fail_count >= opts->software_fallback)
ctx->hwdec_failed = true;
@ -1148,7 +1018,8 @@ static bool receive_frame(struct dec_video *vd, struct mp_image **out_image)
if (!res)
return progress;
if (ctx->hwdec && ctx->hwdec->copying && (res->fmt.flags & MP_IMGFLAG_HWACCEL))
if (ctx->use_hwdec && ctx->hwdec.copying &&
(res->fmt.flags & MP_IMGFLAG_HWACCEL))
{
struct mp_image *sw = mp_image_hw_download(res, ctx->hwdec_swpool);
mp_image_unrefp(&res);
@ -1162,9 +1033,9 @@ static bool receive_frame(struct dec_video *vd, struct mp_image **out_image)
}
if (!ctx->hwdec_notified && vd->opts->hwdec_api != HWDEC_NONE) {
if (ctx->hwdec) {
if (ctx->use_hwdec) {
MP_INFO(vd, "Using hardware decoding (%s).\n",
m_opt_choice_str(mp_hwdec_names, ctx->hwdec->type));
ctx->hwdec.method_name);
} else {
MP_VERBOSE(vd, "Using software decoding.\n");
}
@ -1196,17 +1067,17 @@ static int control(struct dec_video *vd, int cmd, void *arg)
AVCodecContext *avctx = ctx->avctx;
if (!avctx)
break;
if (ctx->hwdec && ctx->hwdec->type == HWDEC_RPI)
if (ctx->use_hwdec && strcmp(ctx->hwdec.method_name, "mmal"))
break; // MMAL has arbitrary buffering, thus unknown
*(int *)arg = avctx->has_b_frames;
return CONTROL_TRUE;
}
case VDCTRL_GET_HWDEC: {
*(int *)arg = ctx->hwdec ? ctx->hwdec->type : 0;
*(char **)arg = ctx->use_hwdec ? ctx->hwdec.method_name : NULL;
return CONTROL_TRUE;
}
case VDCTRL_FORCE_HWDEC_FALLBACK:
if (ctx->hwdec) {
if (ctx->use_hwdec) {
force_fallback(vd);
return ctx->avctx ? CONTROL_OK : CONTROL_ERROR;
}

View File

@ -1,6 +1,8 @@
#include <pthread.h>
#include <assert.h>
#include <libavutil/hwcontext.h>
#include "config.h"
#include "hwdec.h"
@ -47,6 +49,26 @@ struct mp_hwdec_ctx *hwdec_devices_get(struct mp_hwdec_devices *devs,
return res;
}
struct AVBufferRef *hwdec_devices_get_lavc(struct mp_hwdec_devices *devs,
int av_hwdevice_type)
{
AVBufferRef *res = NULL;
pthread_mutex_lock(&devs->lock);
for (int n = 0; n < devs->num_hwctxs; n++) {
struct mp_hwdec_ctx *dev = devs->hwctxs[n];
if (dev->av_device_ref) {
AVHWDeviceContext *hwctx = (void *)dev->av_device_ref->data;
if (hwctx->type == av_hwdevice_type) {
if (dev->av_device_ref)
res = av_buffer_ref(dev->av_device_ref);
break;
}
}
}
pthread_mutex_unlock(&devs->lock);
return res;
}
struct mp_hwdec_ctx *hwdec_devices_get_first(struct mp_hwdec_devices *devs)
{
pthread_mutex_lock(&devs->lock);

View File

@ -5,7 +5,7 @@
struct mp_image_pool;
// keep in sync with --hwdec option (see mp_hwdec_names)
// (for some legacy stuff)
enum hwdec_type {
HWDEC_NONE = 0,
HWDEC_AUTO,
@ -34,9 +34,6 @@ enum hwdec_type {
#define HWDEC_IS_AUTO(x) ((x) == HWDEC_AUTO || (x) == HWDEC_AUTO_COPY)
// hwdec_type names (options.c)
extern const struct m_opt_choice_alternatives mp_hwdec_names[];
struct mp_hwdec_ctx {
enum hwdec_type type; // (never HWDEC_NONE or HWDEC_IS_AUTO)
const char *driver_name; // NULL if unknown/not loaded
@ -79,6 +76,14 @@ void hwdec_devices_destroy(struct mp_hwdec_devices *devs);
struct mp_hwdec_ctx *hwdec_devices_get(struct mp_hwdec_devices *devs,
enum hwdec_type type);
struct AVBufferRef;
// Like hwdec_devices_get(), but search by AV_HWDEVICE_TYPE_* type.
// Contains a wrapped AVHWDeviceContext.
// Beware that this creates a _new_ reference.
struct AVBufferRef *hwdec_devices_get_lavc(struct mp_hwdec_devices *devs,
int av_hwdevice_type);
// For code which still strictly assumes there is 1 (or none) device.
struct mp_hwdec_ctx *hwdec_devices_get_first(struct mp_hwdec_devices *devs);