[FFmpeg-devel] [PATCH v3 2/5] ffmpeg: initialisation code for VAAPI, hwaccel helper

wm4 nfxjfg at googlemail.com
Tue Jan 19 13:16:33 CET 2016


On Mon, 18 Jan 2016 22:50:50 +0000
Mark Thompson <sw at jkqxz.net> wrote:

> ---
>  Makefile       |   1 +
>  ffmpeg.c       |   5 +
>  ffmpeg.h       |   5 +
>  ffmpeg_opt.c   |  14 ++
>  ffmpeg_vaapi.c | 622 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  5 files changed, 647 insertions(+)
>  create mode 100644 ffmpeg_vaapi.c
> 
> diff --git a/Makefile b/Makefile
> index 7836a20..be1d2ca 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -36,6 +36,7 @@ OBJS-ffmpeg-$(CONFIG_VDA)     += ffmpeg_videotoolbox.o
>  endif
>  OBJS-ffmpeg-$(CONFIG_VIDEOTOOLBOX) += ffmpeg_videotoolbox.o
>  OBJS-ffmpeg-$(CONFIG_LIBMFX)  += ffmpeg_qsv.o
> +OBJS-ffmpeg-$(CONFIG_VAAPI)   += ffmpeg_vaapi.o
>  OBJS-ffserver                 += ffserver_config.o
> 
>  TESTTOOLS   = audiogen videogen rotozoom tiny_psnr tiny_ssim base64
> diff --git a/ffmpeg.c b/ffmpeg.c
> index 1f4277c..e76879a 100644
> --- a/ffmpeg.c
> +++ b/ffmpeg.c
> @@ -2603,6 +2603,11 @@ static int init_output_stream(OutputStream *ost, char *error, int error_len)
>              !av_dict_get(ost->encoder_opts, "ab", NULL, 0))
>              av_dict_set(&ost->encoder_opts, "b", "128000", 0);
> 
> +#if CONFIG_VAAPI
> +        if(ost->enc->type == AVMEDIA_TYPE_VIDEO)
> +            vaapi_hardware_set_options(&ost->encoder_opts);
> +#endif
> +
>          if ((ret = avcodec_open2(ost->enc_ctx, codec, &ost->encoder_opts)) < 0) {
>              if (ret == AVERROR_EXPERIMENTAL)
>                  abort_codec_experimental(codec, 1);
> diff --git a/ffmpeg.h b/ffmpeg.h
> index 20322b0..2134213 100644
> --- a/ffmpeg.h
> +++ b/ffmpeg.h
> @@ -65,6 +65,7 @@ enum HWAccelID {
>      HWACCEL_VDA,
>      HWACCEL_VIDEOTOOLBOX,
>      HWACCEL_QSV,
> +    HWACCEL_VAAPI,
>  };
> 
>  typedef struct HWAccel {
> @@ -577,5 +578,9 @@ int vda_init(AVCodecContext *s);
>  int videotoolbox_init(AVCodecContext *s);
>  int qsv_init(AVCodecContext *s);
>  int qsv_transcode_init(OutputStream *ost);
> +int vaapi_decode_init(AVCodecContext *s);
> +
> +int vaapi_hardware_init(const char *device);
> +int vaapi_hardware_set_options(AVDictionary **dict);
> 
>  #endif /* FFMPEG_H */
> diff --git a/ffmpeg_opt.c b/ffmpeg_opt.c
> index 9b341cf..47ac467 100644
> --- a/ffmpeg_opt.c
> +++ b/ffmpeg_opt.c
> @@ -82,6 +82,9 @@ const HWAccel hwaccels[] = {
>  #if CONFIG_LIBMFX
>      { "qsv",   qsv_init,   HWACCEL_QSV,   AV_PIX_FMT_QSV },
>  #endif
> +#if CONFIG_VAAPI
> +    { "vaapi", vaapi_decode_init, HWACCEL_VAAPI, AV_PIX_FMT_VAAPI },
> +#endif
>      { 0 },
>  };
> 
> @@ -442,6 +445,13 @@ static int opt_sdp_file(void *optctx, const char *opt, const char *arg)
>      return 0;
>  }
> 
> +static int opt_vaapi(void *optctx, const char *opt, const char *arg)
> +{
> +    if(vaapi_hardware_init(arg))
> +        exit_program(1);
> +    return 0;
> +}
> +
>  /**
>   * Parse a metadata specifier passed as 'arg' parameter.
>   * @param arg  metadata string to parse
> @@ -3438,5 +3448,9 @@ const OptionDef options[] = {
>      { "dn", OPT_BOOL | OPT_VIDEO | OPT_OFFSET | OPT_INPUT | OPT_OUTPUT, { .off = OFFSET(data_disable) },
>          "disable data" },
> 
> +#if CONFIG_VAAPI
> +    { "vaapi", HAS_ARG, { .func_arg = opt_vaapi }, "set VAAPI hardware context" },
> +#endif
> +
>      { NULL, },
>  };
> diff --git a/ffmpeg_vaapi.c b/ffmpeg_vaapi.c
> new file mode 100644
> index 0000000..e04532c
> --- /dev/null
> +++ b/ffmpeg_vaapi.c
> @@ -0,0 +1,622 @@
> +/*
> + * VAAPI helper for hardware-accelerated decoding.
> + *
> + * Copyright (C) 2016 Mark Thompson <mrt at jkqxz.net>
> + *
> + * This file is part of FFmpeg.
> + *
> + * FFmpeg is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * FFmpeg is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with FFmpeg; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
> + */
> +
> +#include <string.h>
> +#include <unistd.h>
> +#include <fcntl.h>
> +
> +#include "ffmpeg.h"
> +
> +#include "libavutil/avassert.h"
> +#include "libavutil/avconfig.h"
> +#include "libavutil/buffer.h"
> +#include "libavutil/frame.h"
> +#include "libavutil/imgutils.h"
> +#include "libavutil/opt.h"
> +#include "libavutil/pixfmt.h"
> +#include "libavutil/thread.h"
> +#include "libavutil/vaapi.h"
> +
> +#include "libavcodec/vaapi.h"
> +
> +#include <va/va_x11.h>
> +#include <va/va_drm.h>
> +
> +
> +static AVClass vaapi_class = {
> +    .class_name = "VAAPI/driver",
> +    .item_name  = av_default_item_name,
> +    .version    = LIBAVUTIL_VERSION_INT,
> +};
> +
> +
> +#define DEFAULT_SURFACES 20
> +
> +typedef struct VAAPIDecoderContext {
> +    const AVClass *class;
> +
> +    AVVAAPIHardwareContext *hardware_context;
> +    AVVAAPIPipelineConfig config;
> +    AVVAAPIPipelineContext codec;
> +    AVVAAPISurfaceConfig output;
> +
> +    int codec_initialised;
> +
> +    AVFrame output_frame;

sizeof(AVFrame) is not part of the libavutil ABI, so you'll have to
allocate it.

> +} VAAPIDecoderContext;
> +
> +
> +static int vaapi_get_buffer(AVCodecContext *s, AVFrame *frame, int flags)
> +{
> +    InputStream *ist = s->opaque;
> +    VAAPIDecoderContext *ctx = ist->hwaccel_ctx;
> +    int err;
> +
> +    av_assert0(frame->format == AV_PIX_FMT_VAAPI);
> +
> +    av_vaapi_lock_hardware_context(ctx->hardware_context);
> +
> +    err = ff_vaapi_get_output_surface(&ctx->codec, frame);
> +
> +    av_vaapi_unlock_hardware_context(ctx->hardware_context);

Shouldn't those ff_vaapi functions lock instead? (I have no strong
opinion.)

> +
> +    return err;
> +}
> +
> +static int vaapi_retrieve_data(AVCodecContext *avctx, AVFrame *input_frame)
> +{
> +    InputStream *ist = avctx->opaque;
> +    VAAPIDecoderContext *ctx = ist->hwaccel_ctx;
> +    AVVAAPISurfaceConfig *output = &ctx->output;
> +    AVVAAPISurface *surface;
> +    AVFrame *output_frame;
> +    int err, copying;
> +
> +    surface = (AVVAAPISurface*)input_frame->buf[0]->data;

I haven't paid attention to this before. Shouldn't this be a vaapi
surface ID according to how libavcodec works?

> +    av_log(ctx, AV_LOG_DEBUG, "Retrieve data from surface %#x (format %#x).\n",
> +           surface->id, output->av_format);
> +
> +    av_vaapi_lock_hardware_context(ctx->hardware_context);
> +
> +    if(output->av_format == AV_PIX_FMT_VAAPI) {
> +        copying = 0;
> +        av_log(ctx, AV_LOG_VERBOSE, "Surface %#x retrieved without copy.\n",
> +               surface->id);

I'm not sure if this (and the one below) deserves a log message, and at
a verbose level at that. If you really want this, a trace level might be
better.

> +
> +    } else {
> +        err = ff_vaapi_map_surface(surface, 1);
> +        if(err) {
> +            av_log(ctx, AV_LOG_ERROR, "Failed to map surface %#x.",
> +                   surface->id);
> +            goto fail;
> +        }
> +
> +        copying = 1;
> +        av_log(ctx, AV_LOG_VERBOSE, "Surface %#x mapped: image %#x data %p.\n",
> +               surface->id, surface->image.image_id, surface->mapped_address);
> +    }
> +
> +    // The actual frame need not fill the surface.
> +    av_assert0(input_frame->width  <= output->width);
> +    av_assert0(input_frame->height <= output->height);
> +
> +    output_frame = &ctx->output_frame;
> +    output_frame->width  = input_frame->width;
> +    output_frame->height = input_frame->height;
> +    output_frame->format = output->av_format;
> +
> +    if(copying) {
> +        err = av_frame_get_buffer(output_frame, 32);
> +        if(err < 0) {
> +            av_log(ctx, AV_LOG_ERROR, "Failed to get output buffer: %d (%s).\n",
> +                   err, av_err2str(err));
> +            goto fail_unmap;
> +        }
> +
> +        err = ff_vaapi_copy_from_surface(output_frame, surface);
> +        if(err < 0) {
> +            av_log(ctx, AV_LOG_ERROR, "Failed to copy frame data: %d (%s).\n",
> +                   err, av_err2str(err));
> +            goto fail_unmap;
> +        }
> +
> +    } else {
> +        // Just copy the hidden ID field.
> +        output_frame->data[3] = input_frame->data[3];
> +    }
> +
> +    err = av_frame_copy_props(output_frame, input_frame);
> +    if(err < 0) {
> +        av_log(ctx, AV_LOG_ERROR, "Failed to copy frame props: %d (%s).\n",
> +               err, av_err2str(err));
> +        goto fail_unmap;
> +    }
> +
> +    av_frame_unref(input_frame);
> +    av_frame_move_ref(input_frame, output_frame);
> +
> +  fail_unmap:
> +    if(copying)
> +        ff_vaapi_unmap_surface(surface, 0);
> +  fail:
> +    av_vaapi_unlock_hardware_context(ctx->hardware_context);
> +    return err;
> +}

This whole thing could be a utility function. (Libav was planning to do
this.)

> +
> +static const struct {
> +    VAProfile from;
> +    VAProfile to;
> +} vaapi_profile_compatibility[] = {
> +#define FT(f, t) { VAProfile ## f, VAProfile ## t }
> +    FT(MPEG2Simple,             MPEG2Main   ),
> +    FT(H263Baseline,     MPEG4AdvancedSimple),
> +    FT(MPEG4Simple,      MPEG4AdvancedSimple),
> +    FT(MPEG4AdvancedSimple,     MPEG4Main   ),
> +    FT(H264ConstrainedBaseline, H264Baseline),
> +    FT(H264Baseline,            H264Main    ), // (Not quite true.)
> +    FT(H264Main,                H264High    ),
> +    FT(VC1Simple,               VC1Main     ),
> +    FT(VC1Main,                 VC1Advanced ),
> +#undef FT

I think all libva drivers report these properly, so this shouldn't be
needed. Also, you can't decode Baseline with Main?

The only exception might be constrained baseline. AFAIK this profile was
to libva later and before that had to be decoded with Main, so if
constrained baseline is not reported by libva, Main should be used.

> +};
> +
> +static int vaapi_find_next_compatible(VAProfile profile)
> +{
> +    int i;
> +    for(i = 0; i < FF_ARRAY_ELEMS(vaapi_profile_compatibility); i++) {
> +        if(vaapi_profile_compatibility[i].from == profile)
> +            return vaapi_profile_compatibility[i].to;
> +    }
> +    return VAProfileNone;
> +}
> +
> +static const struct {
> +    enum AVCodecID codec_id;
> +    int codec_profile;
> +    VAProfile va_profile;
> +} vaapi_profile_map[] = {
> +#define MAP(c, p, v) { AV_CODEC_ID_ ## c, FF_PROFILE_ ## p, VAProfile ## v }
> +    MAP(MPEG2VIDEO,  MPEG2_SIMPLE,    MPEG2Simple ),
> +    MAP(MPEG2VIDEO,  MPEG2_MAIN,      MPEG2Main   ),
> +    MAP(H263,        UNKNOWN,         H263Baseline),
> +    MAP(MPEG4,       MPEG4_SIMPLE,    MPEG4Simple ),
> +    MAP(MPEG4,       MPEG4_ADVANCED_SIMPLE,
> +                               MPEG4AdvancedSimple),
> +    MAP(MPEG4,       MPEG4_MAIN,      MPEG4Main   ),
> +    MAP(H264,        H264_CONSTRAINED_BASELINE,
> +                           H264ConstrainedBaseline),
> +    MAP(H264,        H264_BASELINE,   H264Baseline),
> +    MAP(H264,        H264_MAIN,       H264Main    ),
> +    MAP(H264,        H264_HIGH,       H264High    ),
> +    MAP(HEVC,        HEVC_MAIN,       HEVCMain    ),
> +    MAP(WMV3,        VC1_SIMPLE,      VC1Simple   ),
> +    MAP(WMV3,        VC1_MAIN,        VC1Main     ),
> +    MAP(WMV3,        VC1_COMPLEX,     VC1Advanced ),
> +    MAP(WMV3,        VC1_ADVANCED,    VC1Advanced ),
> +    MAP(VC1,         VC1_SIMPLE,      VC1Simple   ),
> +    MAP(VC1,         VC1_MAIN,        VC1Main     ),
> +    MAP(VC1,         VC1_COMPLEX,     VC1Advanced ),
> +    MAP(VC1,         VC1_ADVANCED,    VC1Advanced ),
> +    MAP(MJPEG,       UNKNOWN,         JPEGBaseline),
> +    MAP(VP8,         UNKNOWN,        VP8Version0_3),
> +    MAP(VP9,         VP9_0,           VP9Profile0 ),
> +#undef MAP
> +};
> +
> +static VAProfile vaapi_find_profile(const AVCodecContext *avctx)
> +{
> +    VAProfile result = VAProfileNone;
> +    int i;
> +    for(i = 0; i < FF_ARRAY_ELEMS(vaapi_profile_map); i++) {
> +        if(avctx->codec_id != vaapi_profile_map[i].codec_id)
> +            continue;
> +        result = vaapi_profile_map[i].va_profile;
> +        if(avctx->profile == vaapi_profile_map[i].codec_profile)
> +            break;
> +        // If there isn't an exact match, we will choose the last (highest)
> +        // profile in the mapping table.

Shouldn't it fail? the decoder will return garbage or worse if the
video uses features not present in the selected profile.

> +    }
> +    return result;
> +}
> +
> +static const struct {
> +    enum AVPixelFormat pix_fmt;
> +    unsigned int fourcc;
> +} vaapi_image_formats[] = {
> +    { AV_PIX_FMT_NV12,    VA_FOURCC_NV12 },
> +    { AV_PIX_FMT_YUV420P, VA_FOURCC_YV12 },
> +};
> +
> +static int vaapi_get_pix_fmt(unsigned int fourcc)
> +{
> +    int i;
> +    for(i = 0; i < FF_ARRAY_ELEMS(vaapi_image_formats); i++)
> +        if(vaapi_image_formats[i].fourcc == fourcc)
> +            return vaapi_image_formats[i].pix_fmt;
> +    return 0;
> +}
> +
> +static int vaapi_build_decoder_config(VAAPIDecoderContext *ctx,
> +                                      AVVAAPIPipelineConfig *config,
> +                                      AVVAAPISurfaceConfig *output,
> +                                      AVCodecContext *avctx)
> +{
> +    VAStatus vas;
> +    int i;
> +
> +    memset(config, 0, sizeof(*config));
> +
> +    // Pick codec profile to use.
> +    {
> +        VAProfile best_profile, profile;
> +        int profile_count;
> +        VAProfile *profile_list;
> +
> +        best_profile = vaapi_find_profile(avctx);
> +        if(best_profile == VAProfileNone) {
> +            av_log(ctx, AV_LOG_ERROR, "VAAPI does not support codec %s.\n",
> +                   avcodec_get_name(avctx->codec_id));
> +            return AVERROR(EINVAL);
> +        }
> +
> +        profile_count = vaMaxNumProfiles(ctx->hardware_context->display);
> +        profile_list = av_calloc(profile_count, sizeof(VAProfile));

Unchecked memory allocation?

> +
> +        vas = vaQueryConfigProfiles(ctx->hardware_context->display,
> +                                    profile_list, &profile_count);
> +        if(vas != VA_STATUS_SUCCESS) {
> +            av_log(ctx, AV_LOG_ERROR, "Failed to query profiles: %d (%s).\n",
> +                   vas, vaErrorStr(vas));
> +            av_free(profile_list);
> +            return AVERROR(EINVAL);
> +        }
> +
> +        profile = best_profile;
> +        while(profile != VAProfileNone) {
> +            for(i = 0; i < profile_count; i++) {
> +                if(profile_list[i] == profile)
> +                    break;
> +            }
> +            if(i < profile_count)
> +                break;
> +
> +            av_log(ctx, AV_LOG_DEBUG, "Hardware does not support codec "
> +                   "profile: %s / %d -> VAProfile %d.\n",
> +                   avcodec_get_name(avctx->codec_id), avctx->profile,
> +                   profile);
> +            profile = vaapi_find_next_compatible(profile);
> +        }
> +
> +        av_free(profile_list);
> +
> +        if(profile == VAProfileNone) {
> +            av_log(ctx, AV_LOG_ERROR, "Hardware does not support codec: "
> +                   "%s / %d.\n", avcodec_get_name(avctx->codec_id),
> +                   avctx->profile);
> +            return AVERROR(EINVAL);
> +        } else if(profile == best_profile) {
> +            av_log(ctx, AV_LOG_INFO, "Hardware supports exact codec: "
> +                   "%s / %d -> VAProfile %d.\n",
> +                   avcodec_get_name(avctx->codec_id), avctx->profile,
> +                   profile);
> +        } else {
> +            av_log(ctx, AV_LOG_INFO, "Hardware supports compatible codec: "
> +                   "%s / %d -> VAProfile %d.\n",
> +                   avcodec_get_name(avctx->codec_id), avctx->profile,
> +                   profile);
> +        }
> +
> +        config->profile = profile;
> +        config->entrypoint = VAEntrypointVLD;
> +    }
> +
> +    // Decide on the internal chroma format.
> +    {
> +        VAConfigAttrib attr;
> +
> +        // Currently the software only supports YUV420, so just make sure
> +        // that the hardware we have does too.
> +
> +        memset(&attr, 0, sizeof(attr));
> +        attr.type = VAConfigAttribRTFormat;
> +        vas = vaGetConfigAttributes(ctx->hardware_context->display, config->profile,
> +                                    VAEntrypointVLD, &attr, 1);
> +        if(vas != VA_STATUS_SUCCESS) {
> +            av_log(ctx, AV_LOG_ERROR, "Failed to fetch config attributes: "
> +                   "%d (%s).\n", vas, vaErrorStr(vas));
> +            return AVERROR(EINVAL);
> +        }
> +        if(!(attr.value & VA_RT_FORMAT_YUV420)) {
> +            av_log(ctx, AV_LOG_ERROR, "Hardware does not support required "
> +                   "chroma format (%#x).\n", attr.value);
> +            return AVERROR(EINVAL);
> +        }
> +
> +        output->rt_format = VA_RT_FORMAT_YUV420;
> +    }
> +
> +    // Decide on the image format.
> +    if(avctx->pix_fmt == AV_PIX_FMT_VAAPI) {
> +        // We are going to be passing through a VAAPI surface directly:
> +        // they will stay as whatever opaque internal format for that time,
> +        // and we never need to make VAImages from them.
> +
> +        av_log(ctx, AV_LOG_INFO, "Using VAAPI opaque output format.\n");
> +
> +        output->av_format = AV_PIX_FMT_VAAPI;
> +        memset(&output->image_format, 0, sizeof(output->image_format));
> +
> +    } else {
> +        int image_format_count;
> +        VAImageFormat *image_format_list;
> +        int pix_fmt;
> +
> +        // We might want to force a change to the output format here
> +        // if we are intending to use VADeriveImage?
> +
> +        image_format_count = vaMaxNumImageFormats(ctx->hardware_context->display);
> +        image_format_list = av_calloc(image_format_count,
> +                                      sizeof(VAImageFormat));
> +
> +        vas = vaQueryImageFormats(ctx->hardware_context->display, image_format_list,
> +                                  &image_format_count);
> +        if(vas != VA_STATUS_SUCCESS) {
> +            av_log(ctx, AV_LOG_ERROR, "Failed to query image formats: "
> +                   "%d (%s).\n", vas, vaErrorStr(vas));
> +            return AVERROR(EINVAL);
> +        }
> +
> +        for(i = 0; i < image_format_count; i++) {
> +            pix_fmt = vaapi_get_pix_fmt(image_format_list[i].fourcc);
> +            if(pix_fmt == AV_PIX_FMT_NONE)
> +                continue;
> +            if(pix_fmt == avctx->pix_fmt)
> +                break;
> +        }
> +        if(i < image_format_count) {
> +            av_log(ctx, AV_LOG_INFO, "Using desired output format %s "
> +                   "(%#x).\n", av_get_pix_fmt_name(pix_fmt),
> +                   image_format_list[i].fourcc);
> +        } else {
> +            for(i = 0; i < image_format_count; i++) {
> +                pix_fmt = vaapi_get_pix_fmt(image_format_list[i].fourcc);
> +                if(pix_fmt != AV_PIX_FMT_NONE)
> +                    break;
> +            }
> +            if(i >= image_format_count) {
> +                av_log(ctx, AV_LOG_ERROR, "No supported output format found.\n");
> +                av_free(image_format_list);
> +                return AVERROR(EINVAL);
> +            }
> +            av_log(ctx, AV_LOG_INFO, "Using alternate output format %s "
> +                   "(%#x).\n", av_get_pix_fmt_name(pix_fmt),
> +                   image_format_list[i].fourcc);
> +        }
> +
> +        output->av_format = pix_fmt;
> +        memcpy(&output->image_format, &image_format_list[i],
> +               sizeof(VAImageFormat));
> +
> +        av_free(image_format_list);
> +    }
> +
> +    // Decide how many reference frames we need.
> +    {
> +        // We should be able to do this in a more sensible way by looking
> +        // at how many reference frames the input stream requires.
> +        output->count = DEFAULT_SURFACES;
> +    }
> +
> +    // Test whether the width and height are within allowable limits.
> +    {
> +        // Unfortunately, we need an active codec pipeline to do this properly
> +        // using vaQuerySurfaceAttributes().  For now, just assume the values
> +        // we got passed are ok.
> +        output->width  = avctx->coded_width;
> +        output->height = avctx->coded_height;

Not sure I understand...

> +    }
> +

Coding-style-wise, these { ... } statements are weird and AFAIK not
found anywhere else in ffmpeg.

> +    return 0;
> +}
> +
> +static void vaapi_decode_uninit(AVCodecContext *avctx)
> +{
> +    InputStream  *ist = avctx->opaque;
> +    VAAPIDecoderContext *ctx = ist->hwaccel_ctx;
> +
> +    if(ctx->codec_initialised) {
> +        av_vaapi_lock_hardware_context(ctx->hardware_context);
> +        ff_vaapi_pipeline_uninit(&ctx->codec);
> +        av_vaapi_unlock_hardware_context(ctx->hardware_context);
> +        ctx->codec_initialised = 0;
> +    }
> +
> +    av_free(ctx);
> +
> +    ist->hwaccel_ctx           = 0;
> +    ist->hwaccel_uninit        = 0;
> +    ist->hwaccel_get_buffer    = 0;
> +    ist->hwaccel_retrieve_data = 0;
> +}
> +
> +
> +static AVVAAPIHardwareContext vaapi_context;
> +static AVClass *vaapi_log = &vaapi_class;
> +
> +int vaapi_decode_init(AVCodecContext *avctx)
> +{
> +    InputStream *ist = avctx->opaque;
> +    VAAPIDecoderContext *ctx;
> +    int err;
> +
> +    if(ist->hwaccel_id != HWACCEL_VAAPI)
> +        return AVERROR(EINVAL);
> +
> +    if(ist->hwaccel_ctx) {
> +        ctx = ist->hwaccel_ctx;
> +
> +        av_vaapi_lock_hardware_context(ctx->hardware_context);
> +
> +        err = ff_vaapi_pipeline_uninit(&ctx->codec);
> +        if(err) {
> +            av_log(ctx, AV_LOG_ERROR, "Unable to reinit; failed to uninit "
> +                   "old codec context: %d (%s).\n", err, av_err2str(err));
> +            goto fail;
> +        }
> +
> +    } else {
> +        if(vaapi_context.decoder_pipeline_config_id != VA_INVALID_ID) {
> +            av_log(&vaapi_log, AV_LOG_ERROR, "Multiple VAAPI decoder "
> +                   "pipelines are not supported!\n");
> +            return AVERROR(EINVAL);
> +        }
> +
> +        ctx = av_mallocz(sizeof(*ctx));
> +        if(!ctx)
> +            return AVERROR(ENOMEM);
> +        ctx->class = &vaapi_class;
> +
> +        ctx->hardware_context = &vaapi_context;
> +
> +        av_vaapi_lock_hardware_context(ctx->hardware_context);
> +    }
> +
> +    err = vaapi_build_decoder_config(ctx, &ctx->config, &ctx->output, avctx);
> +    if(err) {
> +        av_log(ctx, AV_LOG_ERROR, "No supported configuration for this codec.");
> +        goto fail;
> +    }
> +
> +    err = ff_vaapi_pipeline_init(&ctx->codec, ctx->hardware_context,
> +                                 &ctx->config, 0, &ctx->output);
> +    if(err) {
> +        av_log(ctx, AV_LOG_ERROR, "Failed to initialise codec context: "
> +               "%d (%s).\n", err, av_err2str(err));
> +        goto fail;
> +    }
> +    ctx->codec_initialised = 1;
> +
> +    av_vaapi_unlock_hardware_context(ctx->hardware_context);
> +
> +    av_log(ctx, AV_LOG_DEBUG, "VAAPI decoder (re)init complete.\n");
> +
> +    ist->hwaccel_ctx           = ctx;
> +    ist->hwaccel_uninit        = vaapi_decode_uninit;
> +    ist->hwaccel_get_buffer    = vaapi_get_buffer;
> +    ist->hwaccel_retrieve_data = vaapi_retrieve_data;
> +
> +    avctx->hwaccel_context = ctx->hardware_context;
> +
> +    ctx->hardware_context->decoder_pipeline_config_id  = ctx->codec.config_id;
> +    ctx->hardware_context->decoder_pipeline_context_id = ctx->codec.context_id;
> +
> +    return 0;
> +
> + fail:
> +    av_vaapi_unlock_hardware_context(ctx->hardware_context);
> +    vaapi_decode_uninit(avctx);
> +    return err;
> +}
> +
> +int vaapi_hardware_set_options(AVDictionary **dict)
> +{
> +    av_log(&vaapi_log, AV_LOG_INFO, "Setting VAAPI hardware_context.\n");
> +    av_dict_set_int(dict, "hardware_context", (int64_t)&vaapi_context, 0);
> +    return 0;
> +}
> +
> +
> +static AVMutex vaapi_mutex;
> +
> +static void vaapi_mutex_lock(void)
> +{
> +    ff_mutex_lock(&vaapi_mutex);
> +}
> +
> +static void vaapi_mutex_unlock(void)
> +{
> +    ff_mutex_unlock(&vaapi_mutex);
> +}
> +
> +int vaapi_hardware_init(const char *device)
> +{
> +    VADisplay display;
> +    int drm_fd;
> +    Display *x11_display;
> +    VAStatus vas;
> +    int major, minor;
> +
> +    if(device && device[0] == '/') {
> +        // Assume a DRM path.
> +
> +        drm_fd = open(device, O_RDWR);
> +        if(drm_fd < 0) {
> +            av_log(&vaapi_log, AV_LOG_ERROR, "Cannot open DRM device %s.\n",
> +                   device);
> +            return AVERROR(errno);

I think the av_log call can overwrite the errno.

> +        }
> +        display = vaGetDisplayDRM(drm_fd);
> +        if(!display) {
> +            av_log(&vaapi_log, AV_LOG_ERROR, "Cannot open the VA display "
> +                   "(from DRM device %s).\n", device);
> +            close(drm_fd);
> +            return AVERROR(EINVAL);
> +        }
> +
> +    } else {
> +        // Assume an X11 display name (or null for default).
> +
> +        x11_display = XOpenDisplay(device); // device might be NULL.
> +        if(!x11_display) {
> +            av_log(&vaapi_log, AV_LOG_ERROR, "Cannot open X11 display %s.\n",
> +                   XDisplayName(device));
> +            return AVERROR(ENOENT);
> +        }
> +        display = vaGetDisplay(x11_display);
> +        if(!display) {
> +            av_log(&vaapi_log, AV_LOG_ERROR, "Cannot open the VA display "
> +                   "(from X11 display %s).\n", XDisplayName(device));
> +            return AVERROR(EINVAL);
> +        }
> +    }
> +
> +    vas = vaInitialize(display, &major, &minor);
> +    if(vas != VA_STATUS_SUCCESS) {
> +        av_log(&vaapi_log, AV_LOG_ERROR, "Failed to initialise VAAPI "
> +               "connection: %d (%s).\n", vas, vaErrorStr(vas));
> +        return AVERROR(EINVAL);
> +    }
> +    av_log(&vaapi_log, AV_LOG_INFO, "Initialised VAAPI connection: "
> +           "version %d.%d\n", major, minor);
> +
> +    ff_mutex_init(&vaapi_mutex, 0);
> +
> +    vaapi_context.display = display;
> +    vaapi_context.lock    = &vaapi_mutex_lock;
> +    vaapi_context.unlock  = &vaapi_mutex_unlock;
> +
> +    vaapi_context.decoder_pipeline_config_id  = VA_INVALID_ID;
> +    vaapi_context.decoder_pipeline_context_id = VA_INVALID_ID;
> +
> +    return 0;
> +}
> +

In general, I think the vaapi profile selection and decoder creation
code could be moved to libavcodec. For vdpau there's already such an
API (av_vdpau_bind_context()), which could serve as inspiration. (Of
course this is not a required change.)


More information about the ffmpeg-devel mailing list