mirror of
https://github.com/mpv-player/mpv
synced 2024-12-22 06:42:03 +00:00
video: check profiles with hardware decoding
We had some code for checking profiles earlier, which was removed in commits2508f38
andadfb71b
. These commits mentioned that (working) hw decoding was sometimes prevented due to profile checking, but I can't find the samples anymore that showed this behavior. Also, I changed my opinion, and I think checking the profiles is something that should be done for better fallback to software decoding behavior. The checks roughly follow VLC's vdpau profile checks, although we do not check codec levels. (VLC's profile checks aren't necessarily completely correct, but they're a welcome help anyway.) Add a --vd-lavc-check-hw-profile option, which skips the profile check.
This commit is contained in:
parent
4aae1ff6de
commit
24897eb94c
@ -2526,6 +2526,13 @@ OPTIONS
|
||||
|
||||
See ``--vd=help`` for a full list of available decoders.
|
||||
|
||||
``--vd-lavc-check-hw-profile=<yes|no>``
|
||||
Check hardware decoder profile (default: yes). If ``no`` is set, the
|
||||
highest profile of the hardware decoder is unconditionally selected, and
|
||||
decoding is forced even if the profile of the video is higher than that.
|
||||
The result is most likely broken decoding, but may also help if the
|
||||
detected or reported profiles are somehow incorrect.
|
||||
|
||||
``--vd-lavc-bitexact``
|
||||
Only use bit-exact algorithms in all decoding steps (for codec testing).
|
||||
|
||||
|
@ -864,6 +864,9 @@ const struct MPOpts mp_default_opts = {
|
||||
.lavfdopts = {
|
||||
.allow_mimetype = 1,
|
||||
},
|
||||
.lavc_param = {
|
||||
.check_hw_profile = 1,
|
||||
},
|
||||
.input = {
|
||||
.key_fifo_size = 7,
|
||||
.doubleclick_time = 300,
|
||||
|
@ -217,6 +217,7 @@ typedef struct MPOpts {
|
||||
char *skip_frame_str;
|
||||
int threads;
|
||||
int bitexact;
|
||||
int check_hw_profile;
|
||||
char *avopt;
|
||||
} lavc_param;
|
||||
|
||||
|
@ -22,6 +22,7 @@ enum hwdec_type {
|
||||
};
|
||||
|
||||
typedef struct lavc_ctx {
|
||||
struct MPOpts *opts;
|
||||
AVCodecContext *avctx;
|
||||
AVFrame *pic;
|
||||
struct vd_lavc_hwdec *hwdec;
|
||||
@ -73,6 +74,18 @@ enum {
|
||||
HWDEC_ERR_NO_CODEC = -3,
|
||||
};
|
||||
|
||||
struct hwdec_profile_entry {
|
||||
enum AVCodecID av_codec;
|
||||
int ff_profile;
|
||||
uint64_t hw_profile;
|
||||
};
|
||||
|
||||
const struct hwdec_profile_entry *hwdec_find_profile(
|
||||
struct lavc_ctx *ctx, const struct hwdec_profile_entry *table);
|
||||
bool hwdec_check_codec_support(const char *decoder,
|
||||
const struct hwdec_profile_entry *table);
|
||||
int hwdec_get_max_refs(struct lavc_ctx *ctx);
|
||||
|
||||
// lavc_dr1.c
|
||||
int mp_codec_get_buffer(AVCodecContext *s, AVFrame *frame);
|
||||
void mp_codec_release_buffer(AVCodecContext *s, AVFrame *frame);
|
||||
|
@ -73,38 +73,28 @@ struct priv {
|
||||
bool printed_readback_warning;
|
||||
};
|
||||
|
||||
struct profile_entry {
|
||||
enum AVCodecID av_codec;
|
||||
int maxrefs;
|
||||
const VAProfile *va_profiles;
|
||||
#define PE(av_codec_id, ff_profile, vdp_profile) \
|
||||
{AV_CODEC_ID_ ## av_codec_id, FF_PROFILE_ ## ff_profile, \
|
||||
VAProfile ## vdp_profile}
|
||||
|
||||
static const struct hwdec_profile_entry profiles[] = {
|
||||
PE(MPEG2VIDEO, MPEG2_MAIN, MPEG2Main),
|
||||
PE(MPEG2VIDEO, MPEG2_SIMPLE, MPEG2Simple),
|
||||
PE(MPEG4, MPEG4_ADVANCED_SIMPLE, MPEG4AdvancedSimple),
|
||||
PE(MPEG4, MPEG4_MAIN, MPEG4Main),
|
||||
PE(MPEG4, MPEG4_SIMPLE, MPEG4Simple),
|
||||
PE(H264, H264_HIGH, H264High),
|
||||
PE(H264, H264_MAIN, H264Main),
|
||||
PE(H264, H264_BASELINE, H264Baseline),
|
||||
PE(VC1, VC1_ADVANCED, VC1Advanced),
|
||||
PE(VC1, VC1_MAIN, VC1Main),
|
||||
PE(VC1, VC1_SIMPLE, VC1Simple),
|
||||
PE(WMV3, VC1_ADVANCED, VC1Advanced),
|
||||
PE(WMV3, VC1_MAIN, VC1Main),
|
||||
PE(WMV3, VC1_SIMPLE, VC1Simple),
|
||||
{0}
|
||||
};
|
||||
|
||||
#define RP(...) __VA_ARGS__
|
||||
|
||||
#define PE(av_codec_id, maxrefs, ...) \
|
||||
{AV_CODEC_ID_ ## av_codec_id, \
|
||||
maxrefs, (const VAProfile[]) {RP __VA_ARGS__, -1}}
|
||||
|
||||
static const struct profile_entry profiles[] = {
|
||||
PE(MPEG2VIDEO, 2, (VAProfileMPEG2Main, VAProfileMPEG2Simple)),
|
||||
PE(H264, 16, (VAProfileH264High, VAProfileH264Main,
|
||||
VAProfileH264Baseline)),
|
||||
PE(WMV3, 2, (VAProfileVC1Main, VAProfileVC1Simple)),
|
||||
PE(VC1, 2, (VAProfileVC1Advanced)),
|
||||
PE(MPEG4, 2, (VAProfileMPEG4Main, VAProfileMPEG4AdvancedSimple,
|
||||
VAProfileMPEG4Simple)),
|
||||
};
|
||||
|
||||
static const struct profile_entry *find_codec(enum AVCodecID id)
|
||||
{
|
||||
for (int n = 0; n < MP_ARRAY_SIZE(profiles); n++) {
|
||||
if (profiles[n].av_codec == id)
|
||||
return &profiles[n];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static const char *str_va_profile(VAProfile profile)
|
||||
{
|
||||
switch (profile) {
|
||||
@ -226,9 +216,9 @@ static int create_decoder(struct lavc_ctx *ctx)
|
||||
|
||||
destroy_decoder(ctx);
|
||||
|
||||
const struct profile_entry *pe = find_codec(ctx->avctx->codec_id);
|
||||
const struct hwdec_profile_entry *pe = hwdec_find_profile(ctx, profiles);
|
||||
if (!pe) {
|
||||
mp_msg(MSGT_VO, MSGL_ERR, "[vaapi] Unknown codec!\n");
|
||||
mp_msg(MSGT_VO, MSGL_ERR, "[vaapi] Unsupported codec or profile.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
@ -241,23 +231,18 @@ static int create_decoder(struct lavc_ctx *ctx)
|
||||
for (int i = 0; i < num_profiles; i++)
|
||||
mp_msg(MSGT_VO, MSGL_DBG2, " %s\n", str_va_profile(va_profiles[i]));
|
||||
|
||||
VAProfile va_profile = -1;
|
||||
for (int n = 0; ; n++) {
|
||||
if (pe->va_profiles[n] == -1)
|
||||
break;
|
||||
if (has_profile(va_profiles, num_profiles, pe->va_profiles[n])) {
|
||||
va_profile = pe->va_profiles[n];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (va_profile == -1) {
|
||||
mp_msg(MSGT_VO, MSGL_ERR, "[vaapi] No decoder profile available.\n");
|
||||
VAProfile va_profile = pe->hw_profile;
|
||||
if (!has_profile(va_profiles, num_profiles, va_profile)) {
|
||||
mp_msg(MSGT_VO, MSGL_ERR,
|
||||
"[vaapi] Decoder profile '%s' not available.\n",
|
||||
str_va_profile(va_profile));
|
||||
goto error;
|
||||
}
|
||||
|
||||
mp_msg(MSGT_VO, MSGL_V, "[vaapi] Using profile '%s'.\n",
|
||||
str_va_profile(va_profile));
|
||||
|
||||
int num_surfaces = pe->maxrefs;
|
||||
int num_surfaces = hwdec_get_max_refs(ctx);
|
||||
if (!is_direct_mapping(p->display)) {
|
||||
mp_msg(MSGT_VO, MSGL_V, "[vaapi] No direct mapping.\n");
|
||||
// Note: not sure why it has to be *=2 rather than +=1.
|
||||
@ -438,7 +423,7 @@ static int probe(struct vd_lavc_hwdec *hwdec, struct mp_hwdec_info *info,
|
||||
{
|
||||
if (!info || !info->vaapi_ctx)
|
||||
return HWDEC_ERR_NO_CTX;
|
||||
if (!find_codec(mp_codec_to_av_codec_id(decoder)))
|
||||
if (!hwdec_check_codec_support(decoder, profiles))
|
||||
return HWDEC_ERR_NO_CODEC;
|
||||
return 0;
|
||||
}
|
||||
@ -450,7 +435,7 @@ static int probe_copy(struct vd_lavc_hwdec *hwdec, struct mp_hwdec_info *info,
|
||||
if (!create_va_dummy_ctx(&dummy))
|
||||
return HWDEC_ERR_NO_CTX;
|
||||
destroy_va_dummy_ctx(&dummy);
|
||||
if (!find_codec(mp_codec_to_av_codec_id(decoder)))
|
||||
if (!hwdec_check_codec_support(decoder, profiles))
|
||||
return HWDEC_ERR_NO_CODEC;
|
||||
return 0;
|
||||
}
|
||||
|
@ -78,6 +78,7 @@ const m_option_t lavc_decode_opts_conf[] = {
|
||||
OPT_STRING("skipframe", lavc_param.skip_frame_str, 0),
|
||||
OPT_INTRANGE("threads", lavc_param.threads, 0, 0, 16),
|
||||
OPT_FLAG_CONSTANTS("bitexact", lavc_param.bitexact, 0, 0, CODEC_FLAG_BITEXACT),
|
||||
OPT_FLAG("check-hw-profile", lavc_param.check_hw_profile, 0),
|
||||
OPT_STRING("o", lavc_param.avopt, 0),
|
||||
{NULL, NULL, 0, 0, 0, 0, NULL}
|
||||
};
|
||||
@ -154,6 +155,47 @@ static enum AVDiscard str2AVDiscard(char *str)
|
||||
return AVDISCARD_DEFAULT;
|
||||
}
|
||||
|
||||
// Find the correct profile entry for the current codec and profile.
|
||||
// Assumes the table has higher profiles first (for each codec).
|
||||
const struct hwdec_profile_entry *hwdec_find_profile(
|
||||
struct lavc_ctx *ctx, const struct hwdec_profile_entry *table)
|
||||
{
|
||||
assert(AV_CODEC_ID_NONE == 0);
|
||||
struct lavc_param *lavc_param = &ctx->opts->lavc_param;
|
||||
enum AVCodecID codec = ctx->avctx->codec_id;
|
||||
int profile = ctx->avctx->profile;
|
||||
// Assume nobody cares about these aspects of the profile
|
||||
if (codec == AV_CODEC_ID_H264)
|
||||
profile &= ~(FF_PROFILE_H264_CONSTRAINED | FF_PROFILE_H264_INTRA);
|
||||
for (int n = 0; table[n].av_codec; n++) {
|
||||
if (table[n].av_codec == codec) {
|
||||
if (table[n].ff_profile == FF_PROFILE_UNKNOWN ||
|
||||
profile == FF_PROFILE_UNKNOWN ||
|
||||
table[n].ff_profile == profile ||
|
||||
!lavc_param->check_hw_profile)
|
||||
return &table[n];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Check codec support, without checking the profile.
|
||||
bool hwdec_check_codec_support(const char *decoder,
|
||||
const struct hwdec_profile_entry *table)
|
||||
{
|
||||
enum AVCodecID codec = mp_codec_to_av_codec_id(decoder);
|
||||
for (int n = 0; table[n].av_codec; n++) {
|
||||
if (table[n].av_codec == codec)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int hwdec_get_max_refs(struct lavc_ctx *ctx)
|
||||
{
|
||||
return ctx->avctx->codec_id == AV_CODEC_ID_H264 ? 16 : 2;
|
||||
}
|
||||
|
||||
static int hwdec_probe(struct vd_lavc_hwdec *hwdec, struct mp_hwdec_info *info,
|
||||
const char *decoder, const char **hw_decoder)
|
||||
{
|
||||
@ -208,6 +250,7 @@ static int init(sh_video_t *sh, const char *decoder)
|
||||
{
|
||||
vd_ffmpeg_ctx *ctx;
|
||||
ctx = sh->context = talloc_zero(NULL, vd_ffmpeg_ctx);
|
||||
ctx->opts = sh->opts;
|
||||
ctx->non_dr1_pool = talloc_steal(ctx, mp_image_pool_new(16));
|
||||
|
||||
if (bstr_endswith0(bstr0(decoder), "_vdpau")) {
|
||||
|
@ -47,18 +47,26 @@ struct profile_entry {
|
||||
int maxrefs;
|
||||
};
|
||||
|
||||
#define PE(av_codec_id, vdp_dcoder_profile, maxrefs) \
|
||||
{AV_CODEC_ID_ ## av_codec_id, \
|
||||
VDP_DECODER_PROFILE_ ## vdp_dcoder_profile, \
|
||||
maxrefs}
|
||||
#define PE(av_codec_id, ff_profile, vdp_profile) \
|
||||
{AV_CODEC_ID_ ## av_codec_id, FF_PROFILE_ ## ff_profile, \
|
||||
VDP_DECODER_PROFILE_ ## vdp_profile}
|
||||
|
||||
static const struct profile_entry profiles[] = {
|
||||
PE(MPEG1VIDEO, MPEG1, 2),
|
||||
PE(MPEG2VIDEO, MPEG2_MAIN, 2),
|
||||
PE(H264, H264_HIGH, 16),
|
||||
PE(WMV3, VC1_MAIN, 2),
|
||||
PE(VC1, VC1_ADVANCED, 2),
|
||||
PE(MPEG4, MPEG4_PART2_ASP,2),
|
||||
static const struct hwdec_profile_entry profiles[] = {
|
||||
PE(MPEG1VIDEO, UNKNOWN, MPEG1),
|
||||
PE(MPEG2VIDEO, MPEG2_MAIN, MPEG2_MAIN),
|
||||
PE(MPEG2VIDEO, MPEG2_SIMPLE, MPEG2_SIMPLE),
|
||||
PE(MPEG4, MPEG4_ADVANCED_SIMPLE, MPEG4_PART2_ASP),
|
||||
PE(MPEG4, MPEG4_SIMPLE, MPEG4_PART2_SP),
|
||||
PE(H264, H264_HIGH, H264_HIGH),
|
||||
PE(H264, H264_MAIN, H264_MAIN),
|
||||
PE(H264, H264_BASELINE, H264_BASELINE),
|
||||
PE(VC1, VC1_ADVANCED, VC1_ADVANCED),
|
||||
PE(VC1, VC1_MAIN, VC1_MAIN),
|
||||
PE(VC1, VC1_SIMPLE, VC1_SIMPLE),
|
||||
PE(WMV3, VC1_ADVANCED, VC1_ADVANCED),
|
||||
PE(WMV3, VC1_MAIN, VC1_MAIN),
|
||||
PE(WMV3, VC1_SIMPLE, VC1_SIMPLE),
|
||||
{0}
|
||||
};
|
||||
|
||||
// libavcodec absolutely wants a non-NULL render callback
|
||||
@ -100,15 +108,6 @@ static int handle_preemption(struct lavc_ctx *ctx)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct profile_entry *find_codec(enum AVCodecID id)
|
||||
{
|
||||
for (int n = 0; n < MP_ARRAY_SIZE(profiles); n++) {
|
||||
if (profiles[n].av_codec == id)
|
||||
return &profiles[n];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool create_vdp_decoder(struct lavc_ctx *ctx)
|
||||
{
|
||||
struct priv *p = ctx->hwdec_priv;
|
||||
@ -121,14 +120,32 @@ 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 = find_codec(ctx->avctx->codec_id);
|
||||
const struct hwdec_profile_entry *pe = hwdec_find_profile(ctx, profiles);
|
||||
if (!pe) {
|
||||
mp_msg(MSGT_VO, MSGL_ERR, "[vdpau] Unknown codec!\n");
|
||||
mp_msg(MSGT_VO, MSGL_ERR, "[vdpau] Unsupported codec or profile.\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
vdp_st = vdp->decoder_create(p->vdp_device, pe->vdp_profile,
|
||||
p->vid_width, p->vid_height, pe->maxrefs,
|
||||
VdpBool supported;
|
||||
uint32_t maxl, maxm, maxw, maxh;
|
||||
vdp_st = vdp->decoder_query_capabilities(p->vdp_device, pe->hw_profile,
|
||||
&supported, &maxl, &maxm,
|
||||
&maxw, &maxh);
|
||||
CHECK_ST_WARNING("Querying VDPAU decoder capabilities");
|
||||
if (!supported) {
|
||||
mp_msg(MSGT_VO, MSGL_ERR,
|
||||
"[vdpau] Codec or profile not supported by hardware.\n");
|
||||
goto fail;
|
||||
}
|
||||
if (p->vid_width > maxw || p->vid_height > maxh) {
|
||||
mp_msg(MSGT_VO, MSGL_ERR, "[vdpau] Video too large.\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
int maxrefs = hwdec_get_max_refs(ctx);
|
||||
|
||||
vdp_st = vdp->decoder_create(p->vdp_device, pe->hw_profile,
|
||||
p->vid_width, p->vid_height, maxrefs,
|
||||
&p->context.decoder);
|
||||
CHECK_ST_WARNING("Failed creating VDPAU decoder");
|
||||
p->context.render = p->vdp->decoder_render;
|
||||
@ -209,7 +226,7 @@ static int probe(struct vd_lavc_hwdec *hwdec, struct mp_hwdec_info *info,
|
||||
{
|
||||
if (!info || !info->vdpau_ctx)
|
||||
return HWDEC_ERR_NO_CTX;
|
||||
if (!find_codec(mp_codec_to_av_codec_id(decoder)))
|
||||
if (!hwdec_check_codec_support(decoder, profiles))
|
||||
return HWDEC_ERR_NO_CODEC;
|
||||
return 0;
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ VDP_FUNCTION(VdpBitmapSurfaceQueryCapabilities, VDP_FUNC_ID_BITMAP_SURFACE_QUERY
|
||||
VDP_FUNCTION(VdpDecoderCreate, VDP_FUNC_ID_DECODER_CREATE, decoder_create)
|
||||
VDP_FUNCTION(VdpDecoderDestroy, VDP_FUNC_ID_DECODER_DESTROY, decoder_destroy)
|
||||
VDP_FUNCTION(VdpDecoderRender, VDP_FUNC_ID_DECODER_RENDER, decoder_render)
|
||||
VDP_FUNCTION(VdpDecoderQueryCapabilities, VDP_FUNC_ID_DECODER_QUERY_CAPABILITIES, decoder_query_capabilities)
|
||||
VDP_FUNCTION(VdpDeviceDestroy, VDP_FUNC_ID_DEVICE_DESTROY, device_destroy)
|
||||
VDP_FUNCTION(VdpGenerateCSCMatrix, VDP_FUNC_ID_GENERATE_CSC_MATRIX, generate_csc_matrix)
|
||||
VDP_FUNCTION(VdpOutputSurfaceCreate, VDP_FUNC_ID_OUTPUT_SURFACE_CREATE, output_surface_create)
|
||||
|
Loading…
Reference in New Issue
Block a user