mirror of
https://github.com/outbackdingo/UltraGrid.git
synced 2026-03-21 22:40:30 +00:00
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>
370 lines
13 KiB
C
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;
|
|
}
|