Files
UltraGrid/src/hwaccel_vaapi.c
Martin Pulec ac110f1b49 vaapi dec.: deduce SW format to reported valid
Set AVHWFramesContext::sw_format to first of av_hwframe_transfer_get_formats().

This is consistent how MPV does that. Fixes NV12 being transmitted
despite AVHWFramesContext::sw_format was set to yuv420p causing chroma
channels corruption (because the nv12 data was misinterpreted as the
latter one) occuring on AMD cards, steps to reproduce:
```
uv -t testcard -c lavc:enc=libx264:safe -d gl --param use-hw-accel=vaapi
```

See also:
<66e30e7f2f>
2023-10-06 11:19:11 +02:00

370 lines
13 KiB
C

/**
* @file hwaccel_vaapi.c
* @author Martin Piatka <piatka@cesnet.cz>
*
* @brief This file contains functions related to hw acceleration
*/
/*
* Copyright (c) 2018-2023 CESNET z.s.p.o.
*
* Redistribution and use in source and binary forms, with or without
* modification, is permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
*
* This product includes software developed by CESNET z.s.p.o.
*
* 4. Neither the name of the CESNET nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#include "config_unix.h"
#include "config_win32.h"
#endif // defined HAVE_CONFIG_H
#include <libavcodec/version.h>
#include <libavutil/pixdesc.h>
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 74, 100)
#include <libavcodec/vaapi.h>
#endif
#include <libavutil/hwcontext_vaapi.h>
#include "debug.h"
#include "hwaccel_libav_common.h"
#include "hwaccel_vaapi.h"
#include "libavcodec/lavc_common.h"
#define DEFAULT_SURFACES 20
#define MOD_NAME "[vaapi] "
struct vaapi_ctx {
AVBufferRef *device_ref;
AVHWDeviceContext *device_ctx;
AVVAAPIDeviceContext *device_vaapi_ctx;
AVBufferRef *hw_frames_ctx;
AVHWFramesContext *frame_ctx;
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 74, 100)
VAProfile va_profile;
VAEntrypoint va_entrypoint;
VAConfigID va_config;
VAContextID va_context;
struct vaapi_context decoder_context;
#endif
};
void vaapi_uninit(struct hw_accel_state *s){
free(s->ctx);
}
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 74, 100)
static const struct {
enum AVCodecID av_codec_id;
int codec_profile;
VAProfile va_profile;
} vaapi_profiles[] = {
{AV_CODEC_ID_MPEG2VIDEO, FF_PROFILE_MPEG2_SIMPLE, VAProfileMPEG2Simple},
{AV_CODEC_ID_MPEG2VIDEO, FF_PROFILE_MPEG2_MAIN, VAProfileMPEG2Main},
{AV_CODEC_ID_H264, FF_PROFILE_H264_CONSTRAINED_BASELINE, VAProfileH264ConstrainedBaseline},
{AV_CODEC_ID_H264, FF_PROFILE_H264_BASELINE, VAProfileH264Baseline},
{AV_CODEC_ID_H264, FF_PROFILE_H264_MAIN, VAProfileH264Main},
{AV_CODEC_ID_H264, FF_PROFILE_H264_HIGH, VAProfileH264High},
#if VA_CHECK_VERSION(0, 37, 0)
{AV_CODEC_ID_HEVC, FF_PROFILE_HEVC_MAIN, VAProfileHEVCMain},
#endif
};
static int vaapi_create_context(struct vaapi_ctx *ctx,
AVCodecContext *codec_ctx)
{
const AVCodecDescriptor *codec_desc;
codec_desc = avcodec_descriptor_get(codec_ctx->codec_id);
if(!codec_desc){
return -1;
}
int profile_count = vaMaxNumProfiles(ctx->device_vaapi_ctx->display);
log_msg(LOG_LEVEL_VERBOSE, "VAAPI Profile count: %d\n", profile_count);
VAProfile *list = av_malloc(profile_count * sizeof(VAProfile));
if(!list){
return -1;
}
VAStatus status = vaQueryConfigProfiles(ctx->device_vaapi_ctx->display,
list, &profile_count);
if(status != VA_STATUS_SUCCESS){
log_msg(LOG_LEVEL_ERROR, "[lavd] Profile query failed: %d (%s)\n", status, vaErrorStr(status));
av_free(list);
return -1;
}
VAProfile profile = VAProfileNone;
int match = 0;
for(unsigned i = 0; i < FF_ARRAY_ELEMS(vaapi_profiles); i++){
if(vaapi_profiles[i].av_codec_id != codec_ctx->codec_id)
continue;
if(vaapi_profiles[i].codec_profile == codec_ctx->profile){
profile = vaapi_profiles[i].va_profile;
break;
}
}
for(int i = 0; i < profile_count; i++){
if(profile == list[i])
match = 1;
}
av_freep(&list);
if(!match){
log_msg(LOG_LEVEL_ERROR, "[lavd] Profile not supported \n");
return -1;
}
ctx->va_profile = profile;
ctx->va_entrypoint = VAEntrypointVLD;
status = vaCreateConfig(ctx->device_vaapi_ctx->display, ctx->va_profile,
ctx->va_entrypoint, 0, 0, &ctx->va_config);
if(status != VA_STATUS_SUCCESS){
log_msg(LOG_LEVEL_ERROR, "[lavd] Create config failed: %d (%s)\n", status, vaErrorStr(status));
return -1;
}
AVVAAPIHWConfig *hwconfig = av_hwdevice_hwconfig_alloc(ctx->device_ref);
if(!hwconfig){
log_msg(LOG_LEVEL_WARNING, "[lavd] Failed to get constraints. Will try to continue anyways...\n");
return 0;
}
hwconfig->config_id = ctx->va_config;
AVHWFramesConstraints *constraints = av_hwdevice_get_hwframe_constraints(ctx->device_ref, hwconfig);
if (!constraints){
log_msg(LOG_LEVEL_WARNING, "[lavd] Failed to get constraints. Will try to continue anyways...\n");
av_freep(&hwconfig);
return 0;
}
if (codec_ctx->coded_width < constraints->min_width ||
codec_ctx->coded_width > constraints->max_width ||
codec_ctx->coded_height < constraints->min_height ||
codec_ctx->coded_height > constraints->max_height)
{
log_msg(LOG_LEVEL_WARNING, "[lavd] VAAPI hw does not support the resolution %dx%d\n",
codec_ctx->coded_width,
codec_ctx->coded_height);
av_hwframe_constraints_free(&constraints);
av_freep(&hwconfig);
return -1;
}
av_hwframe_constraints_free(&constraints);
av_freep(&hwconfig);
return 0;
}
#endif //LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 74, 100)
/**
* Returns first SW format from valid_sw_formats. This is usually
* AV_PIX_FMT_YUV420P or AV_PIX_FMT_NV12.
*
* The code borrows heavily from mpv
* <https://github.com/mpv-player/mpv/blob/master/video/out/hwdec/hwdec_vaapi.c>
* namely from function try_format_config().
*/
static enum AVPixelFormat
get_sw_format(VADisplay display, AVBufferRef *device_ref,
enum AVPixelFormat fallback_fmt)
{
enum AVPixelFormat ret = AV_PIX_FMT_NONE;
AVVAAPIHWConfig *hwconfig = NULL;
VAConfigID config_id = 0;
AVHWFramesConstraints *fc = NULL;
VAStatus status = vaCreateConfig(
display, VAProfileNone, VAEntrypointVideoProc, NULL, 0, &config_id);
if (status != VA_STATUS_SUCCESS) {
MSG(ERROR, "cannot create config\n");
goto fail;
}
fc = av_hwdevice_get_hwframe_constraints(device_ref, hwconfig);
if (!fc) {
MSG(ERROR, "failed to retrieve libavutil frame constraints\n");
goto fail;
}
/*
* We need a hwframe_ctx to be able to get the valid formats, but to
* initialise it, we need a format, so we get the first format from the
* hwconfig. We don't care about the other formats in the config because
* the transfer formats list will already include them.
*/
AVBufferRef *fref = NULL;
fref = av_hwframe_ctx_alloc(device_ref);
if (!fref) {
MSG(ERROR, "failed to alloc libavutil frame context\n");
goto fail;
}
AVHWFramesContext *fctx = (void *) fref->data;
enum {
INIT_SIZE = 128, ///< just some valid size
};
fctx->format = AV_PIX_FMT_VAAPI;
fctx->sw_format = fc->valid_sw_formats[0];
fctx->width = INIT_SIZE;
fctx->height = INIT_SIZE;
if (av_hwframe_ctx_init(fref) < 0) {
MSG(ERROR, "failed to init libavutil frame context\n");
goto fail;
}
enum AVPixelFormat *fmts = NULL;
int rc = av_hwframe_transfer_get_formats(
fref, AV_HWFRAME_TRANSFER_DIRECTION_FROM, &fmts, 0);
if (rc) {
MSG(ERROR, "failed to get libavutil frame context supported "
"formats\n");
goto fail;
}
MSG(DEBUG, "Available HW layouts: %s\n", get_avpixfmts_names(fmts));
ret = fmts[0];
fail:
av_hwframe_constraints_free(&fc);
av_buffer_unref(&fref);
if (ret == AV_PIX_FMT_NONE) {
MSG(WARNING, "Using fallback HW frames layout: %s\n",
av_get_pix_fmt_name(ret));
ret = fallback_fmt;
}
MSG(VERBOSE, "Selected HW frames layout: %s\n",
av_get_pix_fmt_name(ret));
return ret;
}
int vaapi_init(struct AVCodecContext *s,
struct hw_accel_state *state,
codec_t out_codec)
{
(void)(out_codec);
struct vaapi_ctx *ctx = calloc(1, sizeof(struct vaapi_ctx));
if(!ctx){
return -1;
}
int ret = create_hw_device_ctx(AV_HWDEVICE_TYPE_VAAPI, &ctx->device_ref);
if(ret < 0)
goto fail;
ctx->device_ctx = (AVHWDeviceContext*)(void *)ctx->device_ref->data;
ctx->device_vaapi_ctx = ctx->device_ctx->hwctx;
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 74, 100)
ret = vaapi_create_context(ctx, s);
if(ret < 0)
goto fail;
#endif
int decode_surfaces = DEFAULT_SURFACES;
if (s->active_thread_type & FF_THREAD_FRAME)
decode_surfaces += s->thread_count;
const enum AVPixelFormat sw_format = get_sw_format(
ctx->device_vaapi_ctx->display, ctx->device_ref, s->sw_pix_fmt);
ret = create_hw_frame_ctx(ctx->device_ref,
s->coded_width,
s->coded_height,
AV_PIX_FMT_VAAPI,
sw_format,
decode_surfaces,
&ctx->hw_frames_ctx);
if(ret < 0)
goto fail;
ctx->frame_ctx = (AVHWFramesContext *)(void *) (ctx->hw_frames_ctx->data);
s->hw_frames_ctx = ctx->hw_frames_ctx;
state->tmp_frame = av_frame_alloc();
if(!state->tmp_frame){
ret = -1;
goto fail;
}
state->type = HWACCEL_VAAPI;
state->copy = true;
state->ctx = ctx;
state->uninit = vaapi_uninit;
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 74, 100)
AVVAAPIFramesContext *avfc = ctx->frame_ctx->hwctx;
VAStatus status = vaCreateContext(ctx->device_vaapi_ctx->display,
ctx->va_config, s->coded_width, s->coded_height,
VA_PROGRESSIVE,
avfc->surface_ids,
avfc->nb_surfaces,
&ctx->va_context);
if(status != VA_STATUS_SUCCESS){
log_msg(LOG_LEVEL_ERROR, "[lavd] Create config failed: %d (%s)\n", status, vaErrorStr(status));
ret = -1;
goto fail;
}
ctx->decoder_context.display = ctx->device_vaapi_ctx->display;
ctx->decoder_context.config_id = ctx->va_config;
ctx->decoder_context.context_id = ctx->va_context;
s->hwaccel_context = &ctx->decoder_context;
#endif //LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 74, 100)
av_buffer_unref(&ctx->device_ref);
return 0;
fail:
av_frame_free(&state->tmp_frame);
av_buffer_unref(&ctx->hw_frames_ctx);
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 74, 100)
if(ctx->device_vaapi_ctx)
vaDestroyConfig(ctx->device_vaapi_ctx->display, ctx->va_config);
#endif
av_buffer_unref(&ctx->device_ref);
free(ctx);
return ret;
}