[FFmpeg-devel] [PATCH v5 2/5] ffmpeg: VAAPI hwaccel helper and related initialisation

Mark Thompson sw at jkqxz.net
Tue Feb 2 21:02:57 CET 2016


On 01/02/16 16:08, wm4 wrote:
> On Sat, 30 Jan 2016 22:12:36 +0000
> Mark Thompson <sw at jkqxz.net> wrote:
> 
>> ---
>>  Makefile       |   1 +
>>  configure      |   5 +
>>  ffmpeg.c       |   6 +
>>  ffmpeg.h       |   9 +
>>  ffmpeg_opt.c   |  38 +++-
>>  ffmpeg_vaapi.c | 642 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>>  6 files changed, 700 insertions(+), 1 deletion(-)
>>  create mode 100644 ffmpeg_vaapi.c
>>
>> diff --git a/Makefile b/Makefile
>> index e484249..b2173bb 100644
>> --- a/Makefile
>> +++ b/Makefile
>> @@ -37,6 +37,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_RECENT) += ffmpeg_vaapi.o
>>  OBJS-ffserver                 += ffserver_config.o
>>
>>  TESTTOOLS   = audiogen videogen rotozoom tiny_psnr tiny_ssim base64
>> diff --git a/configure b/configure
>> index e7f53af..d429cbb 100755
>> --- a/configure
>> +++ b/configure
>> @@ -1965,6 +1965,7 @@ HAVE_LIST="
>>      section_data_rel_ro
>>      texi2html
>>      threads
>> +    vaapi_drm
>>      vaapi_x11
>>      vdpau_x11
>>      winrt
>> @@ -5777,6 +5778,10 @@ enabled vaapi && enabled xlib &&
>>      check_lib2 "va/va.h va/va_x11.h" vaGetDisplay -lva -lva-x11 &&
>>      enable vaapi_x11
>>
>> +enabled vaapi &&
>> +    check_lib2 "va/va.h va/va_drm.h" vaGetDisplayDRM -lva -lva-drm &&
>> +    enable vaapi_drm
>> +
>>  enabled vdpau &&
>>      check_cpp_condition vdpau/vdpau.h "defined VDP_DECODER_PROFILE_MPEG4_PART2_ASP" ||
>>      disable vdpau
>> diff --git a/ffmpeg.c b/ffmpeg.c
>> index a5ec3c3..6616a07 100644
>> --- a/ffmpeg.c
>> +++ b/ffmpeg.c
>> @@ -2603,6 +2603,12 @@ 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_RECENT
>> +        if(ost->enc->type == AVMEDIA_TYPE_VIDEO &&
>> +           strstr(ost->enc->name, "vaapi"))
>> +            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..85173a8 100644
>> --- a/ffmpeg.h
>> +++ b/ffmpeg.h
>> @@ -65,6 +65,7 @@ enum HWAccelID {
>>      HWACCEL_VDA,
>>      HWACCEL_VIDEOTOOLBOX,
>>      HWACCEL_QSV,
>> +    HWACCEL_VAAPI,
>>  };
>>
>>  typedef struct HWAccel {
>> @@ -126,6 +127,8 @@ typedef struct OptionsContext {
>>      int        nb_hwaccels;
>>      SpecifierOpt *hwaccel_devices;
>>      int        nb_hwaccel_devices;
>> +    SpecifierOpt *hwaccel_output_formats;
>> +    int        nb_hwaccel_output_formats;
>>      SpecifierOpt *autorotate;
>>      int        nb_autorotate;
>>
>> @@ -325,6 +328,7 @@ typedef struct InputStream {
>>      /* hwaccel options */
>>      enum HWAccelID hwaccel_id;
>>      char  *hwaccel_device;
>> +    enum AVPixelFormat hwaccel_output_format;
>>
>>      /* hwaccel context */
>>      enum HWAccelID active_hwaccel_id;
>> @@ -540,6 +544,7 @@ extern AVIOContext *progress_avio;
>>  extern float max_error_rate;
>>  extern int vdpau_api_ver;
>>  extern char *videotoolbox_pixfmt;
>> +extern int hwaccel_lax_profile_check;
>>
>>  extern const AVIOInterruptCB int_cb;
>>
>> @@ -577,5 +582,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 669976b..d65293e 100644
>> --- a/ffmpeg_opt.c
>> +++ b/ffmpeg_opt.c
>> @@ -82,8 +82,12 @@ const HWAccel hwaccels[] = {
>>  #if CONFIG_LIBMFX
>>      { "qsv",   qsv_init,   HWACCEL_QSV,   AV_PIX_FMT_QSV },
>>  #endif
>> +#if CONFIG_VAAPI_RECENT
>> +    { "vaapi", vaapi_decode_init, HWACCEL_VAAPI, AV_PIX_FMT_VAAPI },
>> +#endif
>>      { 0 },
>>  };
>> +int hwaccel_lax_profile_check = 0;
>>
>>  char *vstats_filename;
>>  char *sdp_filename;
>> @@ -442,6 +446,15 @@ static int opt_sdp_file(void *optctx, const char *opt, const char *arg)
>>      return 0;
>>  }
>>
>> +#if CONFIG_VAAPI_RECENT
>> +static int opt_vaapi(void *optctx, const char *opt, const char *arg)
>> +{
>> +    if(vaapi_hardware_init(arg))
>> +        exit_program(1);
>> +    return 0;
>> +}
>> +#endif
>> +
>>  /**
>>   * Parse a metadata specifier passed as 'arg' parameter.
>>   * @param arg  metadata string to parse
>> @@ -633,7 +646,8 @@ static void add_input_streams(OptionsContext *o, AVFormatContext *ic)
>>          AVStream *st = ic->streams[i];
>>          AVCodecContext *dec = st->codec;
>>          InputStream *ist = av_mallocz(sizeof(*ist));
>> -        char *framerate = NULL, *hwaccel = NULL, *hwaccel_device = NULL;
>> +        char *framerate = NULL;
>> +        char *hwaccel = NULL, *hwaccel_device = NULL, *hwaccel_output_format = NULL;
>>          char *codec_tag = NULL;
>>          char *next;
>>          char *discard_str = NULL;
>> @@ -753,6 +767,18 @@ static void add_input_streams(OptionsContext *o, AVFormatContext *ic)
>>                  if (!ist->hwaccel_device)
>>                      exit_program(1);
>>              }
>> +
>> +            MATCH_PER_STREAM_OPT(hwaccel_output_formats, str, hwaccel_output_format, ic, st);
>> +            if (hwaccel_output_format) {
>> +                ist->hwaccel_output_format = av_get_pix_fmt(hwaccel_output_format);
>> +                if (ist->hwaccel_output_format == AV_PIX_FMT_NONE) {
>> +                    av_log(NULL, AV_LOG_FATAL, "Unrecognised hwaccel output "
>> +                           "format: %s", hwaccel_output_format);
>> +                }
>> +            } else {
>> +                ist->hwaccel_output_format = AV_PIX_FMT_NONE;
>> +            }
>> +
>>              ist->hwaccel_pix_fmt = AV_PIX_FMT_NONE;
>>
>>              break;
>> @@ -3351,6 +3377,9 @@ const OptionDef options[] = {
>>      { "hwaccel_device",   OPT_VIDEO | OPT_STRING | HAS_ARG | OPT_EXPERT |
>>                            OPT_SPEC | OPT_INPUT,                                  { .off = OFFSET(hwaccel_devices) },
>>          "select a device for HW acceleration", "devicename" },
>> +    { "hwaccel_output_format", OPT_VIDEO | OPT_STRING | HAS_ARG | OPT_EXPERT |
>> +                          OPT_SPEC | OPT_INPUT,                                  { .off = OFFSET(hwaccel_output_formats) },
>> +        "select output format used with HW accelerated decoding", "format" },
>>  #if HAVE_VDPAU_X11
>>      { "vdpau_api_ver", HAS_ARG | OPT_INT | OPT_EXPERT, { &vdpau_api_ver }, "" },
>>  #endif
>> @@ -3359,6 +3388,8 @@ const OptionDef options[] = {
>>  #endif
>>      { "hwaccels",         OPT_EXIT,                                              { .func_arg = show_hwaccels },
>>          "show available HW acceleration methods" },
>> +    { "hwaccel_lax_profile_check", OPT_BOOL | OPT_EXPERT, { &hwaccel_lax_profile_check},
>> +        "attempt to decode anyway if HW accelerated decoder's supported profiles do not exactly match the stream" },
>>      { "autorotate",       HAS_ARG | OPT_BOOL | OPT_SPEC |
>>                            OPT_EXPERT | OPT_INPUT,                                { .off = OFFSET(autorotate) },
>>          "automatically insert correct rotate filters" },
>> @@ -3445,5 +3476,10 @@ const OptionDef options[] = {
>>      { "dn", OPT_BOOL | OPT_VIDEO | OPT_OFFSET | OPT_INPUT | OPT_OUTPUT, { .off = OFFSET(data_disable) },
>>          "disable data" },
>>
>> +#if CONFIG_VAAPI_RECENT
>> +    { "vaapi", HAS_ARG | OPT_EXPERT, { .func_arg = opt_vaapi },
>> +        "set VAAPI hardware context (DRM path or X11 display name)" },
>> +#endif
>> +
>>      { NULL, },
>>  };
>> diff --git a/ffmpeg_vaapi.c b/ffmpeg_vaapi.c
>> new file mode 100644
>> index 0000000..a6a2018
>> --- /dev/null
>> +++ b/ffmpeg_vaapi.c
>> @@ -0,0 +1,642 @@
>> +/*
>> + * 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 <fcntl.h>
>> +#include <pthread.h>
>> +#include <unistd.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 "libavcodec/vaapi.h"
>> +#include "libavcodec/vaapi_support.h"
>> +
>> +#include <va/va_x11.h>
>> +#include <va/va_drm.h>
>> +
>> +
>> +static AVClass vaapi_class = {
>> +    .class_name = "vaapi_ffmpeg",
>> +    .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;
>> +    AVVAAPISurfacePool pool;
>> +
>> +    enum AVPixelFormat output_format;
>> +    int codec_initialised;
>> +} VAAPIDecoderContext;
>> +
>> +
>> +static int vaapi_get_buffer(AVCodecContext *s, AVFrame *frame, int flags)
>> +{
>> +    InputStream *ist = s->opaque;
>> +    VAAPIDecoderContext *ctx = ist->hwaccel_ctx;
>> +
>> +    av_assert0(frame->format == AV_PIX_FMT_VAAPI);
>> +
>> +    av_vaapi_surface_pool_get(&ctx->pool, frame);
>> +
>> +    av_log(ctx, AV_LOG_DEBUG, "Decoder given reference to surface %#x.\n",
>> +           (VASurfaceID)frame->data[3]);
>> +
>> +    return 0;
>> +}
>> +
>> +static int vaapi_retrieve_data(AVCodecContext *avctx, AVFrame *input_image)
>> +{
>> +    InputStream *ist = avctx->opaque;
>> +    VAAPIDecoderContext *ctx = ist->hwaccel_ctx;
>> +    AVVAAPISurfaceConfig *output = &ctx->output;
>> +    AVFrame *output_image;
>> +    int err;
>> +
>> +    av_log(ctx, AV_LOG_DEBUG, "Decoder output in surface %#x.\n",
>> +           (VASurfaceID)input_image->data[3]);
>> +
>> +    if(ctx->output_format == AV_PIX_FMT_VAAPI) {
>> +        // Nothing to do, the hardware surface is passed to the output.
>> +        goto done;
>> +    }
>> +
>> +    output_image = av_frame_alloc();
>> +    if(!output_image)
>> +        return AVERROR(ENOMEM);
>> +
>> +    output_image->format = output->av_format;
>> +    output_image->width  = input_image->width;
>> +    output_image->height = input_image->height;
>> +
>> +    err = av_frame_get_buffer(output_image, 32);
>> +    if(err < 0) {
>> +        av_log(ctx, AV_LOG_ERROR, "Failed to get buffer for output frame: "
>> +               "%d (%s).\n", err, av_err2str(err));
>> +        return err;
>> +    }
>> +
>> +    err = av_vaapi_copy_from_surface(output_image, input_image);
>> +    if(err < 0) {
>> +        av_log(ctx, AV_LOG_ERROR, "Failed to copy from output surface: "
>> +               "%d (%s).\n", err, av_err2str(err));
>> +        return err;
>> +    }
>> +
>> +    av_frame_copy_props(output_image, input_image);
>> +    av_frame_unref(input_image);
>> +    av_frame_move_ref(input_image, output_image);
>> +
>> +  done:
>> +    av_log(ctx, AV_LOG_DEBUG, "Decoder output: %s, %ux%u.\n",
>> +           av_get_pix_fmt_name(input_image->format),
>> +           input_image->width, input_image->height);
>> +    return 0;
>> +}
>> +
>> +
>> +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    ),
>> +#if CONFIG_HEVC_VAAPI_HWACCEL
>> +    MAP(HEVC,        HEVC_MAIN,       HEVCMain    ),
>> +#endif
>> +    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 ),
>> +#if CONFIG_VP9_VAAPI_HWACCEL
>> +    MAP(VP9,         VP9_0,           VP9Profile0 ),
>> +#endif
>> +#undef MAP
>> +};

Thinking a bit more clearly about this structure, actually everything in it should be conditional on the relevant CONFIG_xxx_VAAPI_HWACCEL.  I'll make that change.

>> +static int vaapi_build_decoder_config(VAAPIDecoderContext *ctx,
>> +                                      AVVAAPIPipelineConfig *config,
>> +                                      AVVAAPISurfaceConfig *output,
>> +                                      AVCodecContext *avctx,
>> +                                      int fallback_allowed)
>> +{
>> +    VAStatus vas;
>> +    int i;
>> +    int loglevel = fallback_allowed ? AV_LOG_VERBOSE : AV_LOG_ERROR;
>> +
>> +    memset(config, 0, sizeof(*config));
>> +
>> +    // Pick codec profile to use.
>> +    {
>> +        VAProfile profile;
>> +        int profile_count, exact_match, alt_profile;
>> +        VAProfile *profile_list;
>> +
>> +        profile = VAProfileNone;
>> +        exact_match = 0;
>> +
>> +        for(i = 0; i < FF_ARRAY_ELEMS(vaapi_profile_map); i++) {
>> +            if(avctx->codec_id != vaapi_profile_map[i].codec_id)
>> +                continue;
>> +            profile = vaapi_profile_map[i].va_profile;
>> +            if(avctx->profile == vaapi_profile_map[i].codec_profile) {
>> +                exact_match = 1;
>> +                break;
>> +            }
>> +            alt_profile = vaapi_profile_map[i].codec_profile;
>> +        }
>> +        if(profile == VAProfileNone) {
>> +            av_log(ctx, loglevel, "VAAPI does not support codec %s.\n",
>> +                   avcodec_get_name(avctx->codec_id));
>> +            return AVERROR(EINVAL);
>> +        }
>> +        if(!exact_match) {
>> +            if(fallback_allowed || !hwaccel_lax_profile_check) {
>> +                av_log(ctx, loglevel, "VAAPI does not support codec %s "
>> +                       "profile %d.\n", avcodec_get_name(avctx->codec_id),
>> +                       avctx->profile);
>> +                if(!fallback_allowed) {
>> +                    av_log(ctx, AV_LOG_WARNING, "If you want attempt decoding "
>> +                           "anyway with a possibly-incompatible profile, add "
>> +                           "the option -hwaccel_lax_profile_check.\n");
>> +                }
>> +                return AVERROR(EINVAL);
>> +            } else {
>> +                av_log(ctx, AV_LOG_WARNING, "VAAPI does not support codec %s "
>> +                       "profile %d: trying instead with profile %d.\n",
>> +                       avcodec_get_name(avctx->codec_id),
>> +                       avctx->profile, alt_profile);
>> +                av_log(ctx, AV_LOG_WARNING, "This may not fail or give "
>> +                       "incorrect results, depending on your hardware.\n");
>> +            }
>> +        }
>> +
>> +        profile_count = vaMaxNumProfiles(ctx->hardware_context->display);
>> +        profile_list = av_calloc(profile_count, sizeof(VAProfile));
>> +        if(!profile_list)
>> +            return AVERROR(ENOMEM);
>> +
>> +        vas = vaQueryConfigProfiles(ctx->hardware_context->display,
>> +                                    profile_list, &profile_count);
>> +        if(vas != VA_STATUS_SUCCESS) {
>> +            av_log(ctx, loglevel, "Failed to query profiles: %d (%s).\n",
>> +                   vas, vaErrorStr(vas));
>> +            av_free(profile_list);
>> +            return AVERROR_EXTERNAL;
>> +        }
>> +
>> +        for(i = 0; i < profile_count; i++) {
>> +            if(profile_list[i] == profile)
>> +                break;
>> +        }
>> +        if(i >= profile_count)
>> +            profile = VAProfileNone;
>> +
>> +        av_free(profile_list);
>> +
>> +        if(profile == VAProfileNone) {
>> +            av_log(ctx, loglevel, "Hardware does not support codec: "
>> +                   "%s / %d.\n", avcodec_get_name(avctx->codec_id),
>> +                   avctx->profile);
>> +            return AVERROR(EINVAL);
>> +        } else {
>> +            av_log(ctx, AV_LOG_DEBUG, "Hardware supports 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, loglevel, "Failed to fetch config attributes: "
>> +                   "%d (%s).\n", vas, vaErrorStr(vas));
>> +            return AVERROR_EXTERNAL;
>> +        }
>> +        if(!(attr.value & VA_RT_FORMAT_YUV420)) {
>> +            av_log(ctx, loglevel, "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(ctx->output_format == 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_DEBUG, "Using VAAPI opaque output format.\n");
>> +
>> +        output->av_format = AV_PIX_FMT_VAAPI;
>> +        memset(&output->image_format, 0, sizeof(output->image_format));
>> +
>> +    } else {
>> +        // Use the user-specified format (-hwaccel_output_format) if given,
>> +        // otherwise try to get NV12 and if not possible just use the first
>> +        // known one in the list (random something, the user receiving it will
>> +        // just have to cope - hopefully a swscale instance can fix it up).
>> +
>> +        int default_format = AV_PIX_FMT_NV12;
>> +        int image_format_count;
>> +        VAImageFormat *image_format_list;
>> +        int pix_fmt;
>> +
>> +        image_format_count = vaMaxNumImageFormats(ctx->hardware_context->display);
>> +        image_format_list = av_calloc(image_format_count,
>> +                                      sizeof(VAImageFormat));
>> +        if(!image_format_list)
>> +            return AVERROR(ENOMEM);
>> +
>> +        vas = vaQueryImageFormats(ctx->hardware_context->display, image_format_list,
>> +                                  &image_format_count);
>> +        if(vas != VA_STATUS_SUCCESS) {
>> +            av_log(ctx, loglevel, "Failed to query image formats: "
>> +                   "%d (%s).\n", vas, vaErrorStr(vas));
>> +            return AVERROR_EXTERNAL;
>> +        }
>> +
>> +        if(ctx->output_format != AV_PIX_FMT_NONE) {
>> +            for(i = 0; i < image_format_count; i++) {
>> +                pix_fmt = av_vaapi_pix_fmt(image_format_list[i].fourcc);
>> +                if(pix_fmt == AV_PIX_FMT_NONE)
>> +                    continue;
>> +                if(pix_fmt == ctx->output_format)
>> +                    break;
>> +            }
>> +            if(i < image_format_count) {
>> +                av_log(ctx, AV_LOG_DEBUG, "Using desired output format %s "
>> +                       "(%#x).\n", av_get_pix_fmt_name(pix_fmt),
>> +                       image_format_list[i].fourcc);
>> +            } else {
>> +                av_log(ctx, loglevel, "Unable to use specified output "
>> +                       "format %s.", av_get_pix_fmt_name(pix_fmt));
>> +                av_free(image_format_list);
>> +                return AVERROR(EINVAL);
>> +            }
>> +        } else {
>> +            for(i = 0; i < image_format_count; i++) {
>> +                pix_fmt = av_vaapi_pix_fmt(image_format_list[i].fourcc);
>> +                if(pix_fmt == default_format)
>> +                    break;
>> +            }
>> +            if(i < image_format_count) {
>> +                av_log(ctx, AV_LOG_DEBUG, "Using default 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 = av_vaapi_pix_fmt(image_format_list[i].fourcc);
>> +                    if(pix_fmt != AV_PIX_FMT_NONE)
>> +                        break;
>> +                }
>> +                if(i >= image_format_count) {
>> +                    av_log(ctx, loglevel, "No supported output format.\n");
>> +                    av_free(image_format_list);
>> +                    return AVERROR(EINVAL);
>> +                }
>> +                av_log(ctx, AV_LOG_DEBUG, "Using random 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;
> 
> If this is disabled, where does the default count come from?

It used to be part of the surface configuration, but moved to the surface pool initialisation in a later iteration (see av_vaapi_surface_pool_init() call below). The constant should be here, though, so I'll put another variable in the decoder context to hold it.

> I don't think it's possible to get the value from the stream at this
> point, only when the per-hwaccel entrypoints in libavcodec are actually
> called.
> 
> (Not sure if I commented on this before.)

Even if this is true, it can still be conditional on the codec.  (You will never need 16 reference frames to decode MPEG-2.)

Also missing is a way to tell how many frames might be held by whatever is attached to the output in the non-copying case.

>> +    }
>> +
>> +    // Test whether the width and height are within allowable limits.
>> +    {
>> +        // It would be nice if we could check this here before starting
>> +        // anything, but unfortunately we need an active VA config to test.
>> +        // Hence just copy.  If it isn't supproted, the pipeline
>> +        // initialisation below will fail below instead.
>> +        config->width  = output->width  = avctx->coded_width;
>> +        config->height = output->height = avctx->coded_height;
>> +    }
>> +
>> +    return 0;
>> +}
>> +
>> +static void vaapi_decode_uninit(AVCodecContext *avctx)
>> +{
>> +    InputStream  *ist = avctx->opaque;
>> +    VAAPIDecoderContext *ctx = ist->hwaccel_ctx;
>> +
>> +    if(ctx) {
>> +        if(ctx->codec_initialised) {
>> +            av_vaapi_lock_hardware_context(ctx->hardware_context);
>> +            av_vaapi_surface_pool_uninit(&ctx->pool);
>> +            av_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;
>> +}
>> +
>> +
>> +AVVAAPIHardwareContext *vaapi_context;
> 
> Should probably be at the start of the file. And be made static.

Ok.  (This wasn't static purely to make the filter testable, because it can't currently reference the context by any other means.)

>> +
>> +int vaapi_decode_init(AVCodecContext *avctx)
>> +{
>> +    InputStream *ist = avctx->opaque;
>> +    VAAPIDecoderContext *ctx;
>> +    int err;
>> +    int loglevel = (ist->hwaccel_id != HWACCEL_VAAPI ? AV_LOG_VERBOSE : AV_LOG_ERROR);
>> +
>> +    // We have -hwaccel without -vaapi, so just initialise here with the
>> +    // device passed as -hwaccel_device (if -vaapi was passed, it will
>> +    // always have been called before now).
>> +    if(!vaapi_context) {
>> +        err = vaapi_hardware_init(ist->hwaccel_device);
>> +        if(err < 0)
>> +            return err;
>> +    }
>> +
>> +    if(ist->hwaccel_ctx) {
>> +        ctx = ist->hwaccel_ctx;
>> +
>> +        av_vaapi_lock_hardware_context(ctx->hardware_context);
>> +
>> +        err = av_vaapi_pipeline_uninit(&ctx->codec);
>> +        if(err < 0) {
>> +            av_log(ctx, loglevel, "Unable to reinit; failed to uninit "
>> +                   "old codec context: %d (%s).\n", err, av_err2str(err));
>> +            goto fail;
>> +        }
>> +
>> +        err = av_vaapi_surface_pool_uninit(&ctx->pool);
>> +        if(err < 0) {
>> +            av_log(ctx, loglevel, "Unable to reinit; failed to uninit "
>> +                   "old surface pool: %d (%s).\n", err, av_err2str(err));
>> +            goto fail;
>> +        }
>> +
>> +    } else {
>> +        if(vaapi_context->decoder_pipeline_config_id != VA_INVALID_ID) {
>> +            av_log(0, loglevel, "Multiple simultaneous VAAPI decode "
>> +                   "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);
>> +    }
>> +
>> +    ctx->output_format = ist->hwaccel_output_format;
>> +
>> +    err = vaapi_build_decoder_config(ctx, &ctx->config, &ctx->output, avctx,
>> +                                     ist->hwaccel_id != HWACCEL_VAAPI);
>> +    if(err < 0) {
>> +        av_log(ctx, loglevel, "No supported configuration for this codec.");
>> +        goto fail;
>> +    }
>> +
>> +    avctx->pix_fmt = ctx->output_format;
>> +
>> +    err = av_vaapi_surface_pool_init(&ctx->pool, ctx->hardware_context,
>> +                                     &ctx->output, DEFAULT_SURFACES);
>> +    if(err < 0) {
>> +        av_log(ctx, loglevel, "Failed to initialise surface pool: "
>> +               "%d (%s).\n", err, av_err2str(err));
>> +        goto fail;
>> +    }
>> +
>> +    err = av_vaapi_pipeline_init(&ctx->codec, ctx->hardware_context,
>> +                                 &ctx->config, &ctx->pool);
>> +    if(err < 0) {
>> +        av_log(ctx, loglevel, "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(0, AV_LOG_DEBUG, "Setting VAAPI hardware_context.\n");
>> +    av_dict_set_int(dict, "hardware_context", (int64_t)vaapi_context, 0);
>> +    return 0;
>> +}
>> +
>> +
>> +static AVClass *vaapi_log = &vaapi_class;
>> +static pthread_mutex_t vaapi_mutex;
>> +
>> +static void vaapi_mutex_lock(void *ignored)
>> +{
>> +    pthread_mutex_lock(&vaapi_mutex);
>> +}
>> +
>> +static void vaapi_mutex_unlock(void *ignored)
>> +{
>> +    pthread_mutex_unlock(&vaapi_mutex);
>> +}
>> +
>> +int vaapi_hardware_init(const char *device)
>> +{
>> +    VADisplay display;
>> +    int drm_fd;
>> +    Display *x11_display;
>> +    VAStatus vas;
>> +    int major, minor, err;
>> +    pthread_mutexattr_t mutex_attr;
>> +
>> +    err = pthread_mutexattr_init(&mutex_attr);
>> +    if(!err)
>> +        err = pthread_mutexattr_settype(&mutex_attr, PTHREAD_MUTEX_RECURSIVE);
>> +    if(!err)
>> +        err = pthread_mutex_init(&vaapi_mutex, &mutex_attr);
>> +    pthread_mutexattr_destroy(&mutex_attr);
>> +    if(err) {
>> +        err = AVERROR(err);
>> +        av_log(&vaapi_log, AV_LOG_ERROR, "Failed to initialise VAAPI mutex: "
>> +               "%s.\n", av_err2str(err));
>> +        return err;
>> +    }
>> +
>> +    display = 0;
>> +
>> +#if HAVE_VAAPI_X11
>> +    if(!display) {
>> +        // Try to open the device as an X11 display.
>> +        x11_display = XOpenDisplay(device);
>> +        if(!x11_display) {
>> +            av_log(&vaapi_log, AV_LOG_WARNING, "Cannot open X11 display "
>> +                   "%s.\n", XDisplayName(device));
>> +        } else {
>> +            display = vaGetDisplay(x11_display);
>> +            if(!display) {
>> +                av_log(&vaapi_log, AV_LOG_WARNING, "Cannot open a VA display "
>> +                       "from X11 display %s.\n", XDisplayName(device));
>> +                XCloseDisplay(x11_display);
>> +            } else {
>> +                av_log(&vaapi_log, AV_LOG_VERBOSE, "Opened VA display via "
>> +                       "X11 display %s.\n", XDisplayName(device));
>> +            }
>> +        }
>> +    }
>> +#endif
>> +
>> +#if HAVE_VAAPI_DRM
>> +    if(!display && device) {
>> +        // Try to open the device as a DRM path.
>> +        drm_fd = open(device, O_RDWR);
>> +        if(drm_fd < 0) {
>> +            err = errno;
>> +            av_log(&vaapi_log, AV_LOG_WARNING, "Cannot open DRM device %s.\n",
>> +                   device);
>> +        } else {
>> +            display = vaGetDisplayDRM(drm_fd);
>> +            if(!display) {
>> +                av_log(&vaapi_log, AV_LOG_WARNING, "Cannot open a VA display "
>> +                       "from DRM device %s.\n", device);
>> +                close(drm_fd);
>> +            } else {
>> +                av_log(&vaapi_log, AV_LOG_VERBOSE, "Opened VA display via "
>> +                       "DRM device %s.\n", device);
>> +            }
>> +        }
>> +    }
>> +#endif
>> +
>> +    if(!display) {
>> +        av_log(&vaapi_log, AV_LOG_ERROR, "No VA display found for "
>> +               "device %s.\n", 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_VERBOSE, "Initialised VAAPI connection: "
>> +           "version %d.%d\n", major, minor);
>> +
>> +    vaapi_context = av_vaapi_alloc_hardware_context();
>> +    if(!vaapi_context)
>> +        return AVERROR(ENOMEM);
>> +
>> +    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;
>> +}



More information about the ffmpeg-devel mailing list